diff --git a/src/config.py b/src/config.py --- a/src/config.py +++ b/src/config.py @@ -4,7 +4,7 @@ import logging logging.basicConfig(format='%(asctime)s %(levelname)s: %(message)s', level=logging.DEBUG) class misc: - version=(0,0) + version=(0,0,0) class gui: showBigPoints=True diff --git a/src/core.py b/src/core.py new file mode 100644 --- /dev/null +++ b/src/core.py @@ -0,0 +1,64 @@ +import multiprocessing +import PIL +from corners import Corners +from gui import gui + + +class Core: + corners=Corners() + tresW=60.0 + tresB=30.0 + + @staticmethod + def setCorners(corners): + Core.corners=corners + + @staticmethod + def setTresholds(tresB=None,tresW=None): + if tresB is not None: Core.tresB=tresB + if tresW is not None: Core.tresW=tresW + + +msgQueue=multiprocessing.Queue() +guiQueue=multiprocessing.Queue() +incomingEvent=multiprocessing.Event() +guiEvent=multiprocessing.Event() + +frame=PIL.Image.open("../images/7.jpg") + +guiProc=multiprocessing.Process(name="gui",target=gui,args=(guiQueue,guiEvent,msgQueue,incomingEvent)) +guiProc.start() +guiQueue.put(("setCurrentFrame",(frame,None),dict())) +guiEvent.set() + +while True: + incomingEvent.wait() + msg=msgQueue.get() + if msgQueue.empty(): + incomingEvent.clear() + print(msg) + + +# !! always pass object copies, don't share instances +""" +core +==== +corners +grid +go +imageAnalyzer + + +gui +=== +a) keeps references to important objects and uses them +b) gets and sets all relevant data through method calls with core + +GUI +<- addCorner(corner) +-> redrawImgView(img,grid) +<- refreshTresholds(tresB,tresW) + +BoardView +-> redrawState(go) +""" \ No newline at end of file diff --git a/src/corners.py b/src/corners.py --- a/src/corners.py +++ b/src/corners.py @@ -1,4 +1,4 @@ -from epoint import * +from epoint import EPoint class Corners: diff --git a/src/grid.py b/src/grid.py --- a/src/grid.py +++ b/src/grid.py @@ -1,5 +1,5 @@ import numpy -from epoint import * +from epoint import EPoint ## Projective transformation of a point with a matrix A. diff --git a/src/gui.py b/src/gui/__init__.py rename from src/gui.py rename to src/gui/__init__.py --- a/src/gui.py +++ b/src/gui/__init__.py @@ -1,30 +1,48 @@ -import tkinter as tk +import threading +import tkinter as tk from PIL import ImageTk import PIL import config -from corners import * -from grid import * -from image_analyzer import * -from go import * +from corners import Corners +import image_analyzer +from go import Go -class Application(tk.Frame): - def __init__(self, master=None): +class MainWindow(tk.Frame): + def __init__(self,parent,master=None): + self.parent=parent self.corners=Corners() + + self.currentFrame=None self.boardGrid=None + self.gameState=None + + self.img=None tk.Frame.__init__(self, master) self.grid(column=0,row=0) - self.createWidgets() + self._createWidgets() + + def setCurrentFrame(self,frame,grid): + self.currentFrame=frame + self.boardGrid=grid - def createWidgets(self): + w=int(self.imgView['width']) + h=int(self.imgView['height']) + self.img=ImageTk.PhotoImage(frame.resize((w,h),resample=PIL.Image.BILINEAR)) + + def setGameState(self,gameState): + pass + + def setCallbacks(self,setCorners,setTresholds): + self.cornersCallback=setCorners + self.tresholdsCallback=setTresholds + + def _createWidgets(self): # a captured frame with overlay graphics self.imgView=tk.Canvas(self) self.imgView.configure(width=480,height=360) - self.imgOrig=PIL.Image.open("../images/7.jpg") - - self.img=ImageTk.PhotoImage(self.imgOrig.resize((int(self.imgView['width']),int(self.imgView['height'])),resample=PIL.Image.BILINEAR)) self.imgView.bind('<1>',lambda e: self.addCorner(e.x,e.y)) @@ -35,12 +53,9 @@ class Application(tk.Frame): self.boardView.grid(column=1,row=0) # more controls below the board - def _refreshTresholds(_): - self.refreshTresholds() - self.redrawImgView() - self.scaleTresB=tk.Scale(self, orient=tk.HORIZONTAL, length=200, from_=0.0, to=100.0, command=_refreshTresholds) - self.scaleTresW=tk.Scale(self, orient=tk.HORIZONTAL, length=200, from_=0.0, to=100.0, command=_refreshTresholds) - self.scaleTresB.set(30.0) + self.scaleTresB=tk.Scale(self, orient=tk.HORIZONTAL, length=200, from_=0.0, to=100.0, command=self.refreshTresholds) + self.scaleTresW=tk.Scale(self, orient=tk.HORIZONTAL, length=200, from_=0.0, to=100.0, command=self.refreshTresholds) + self.scaleTresB.set(30.0) # !! proper defaults self.scaleTresW.set(60.0) self.scaleTresB.grid(column=0,row=1,columnspan=2) self.scaleTresW.grid(column=0,row=2,columnspan=2) @@ -52,21 +67,23 @@ class Application(tk.Frame): def addCorner(self,x,y): self.corners.add(x,y) if self.corners.canonizeOrder(): - self.boardGrid=Grid(self.corners) - self.boardView.setBoardGrid(self.boardGrid) - + self.parent.sendMsg("setCorners",(self.corners,)) + # self.boardGrid=Grid(self.corners) + # self.boardView.setBoardGrid(self.boardGrid) + # self.redrawImgView() ## Redraws the current image and its overlay. def redrawImgView(self): - self.imgView.create_image(2,2,anchor="nw",image=self.img) + if self.currentFrame and self.img: + self.imgView.create_image(2,2,anchor="nw",image=self.img) - origWidth,origHeight=self.imgOrig.size - widthRatio=origWidth/self.img.width() - heightRatio=origHeight/self.img.height() - sizeCoef=max(widthRatio,heightRatio) - tresB=self.scaleTresB.get() - tresW=self.scaleTresW.get() + origWidth,origHeight=self.currentFrame.size + widthRatio=origWidth/self.img.width() + heightRatio=origHeight/self.img.height() + sizeCoef=max(widthRatio,heightRatio) + + # shift=EPoint(origWidth-self.img.width()*sizeCoef,origHeight-self.img.height()*sizeCoef)/2 for corner in self.corners.corners: self.markPoint(corner.x,corner.y) @@ -84,15 +101,11 @@ class Application(tk.Frame): if self.boardGrid!=None and config.gui.showBigPoints: for r in range(19): for c in range(19): - ((r1,c1),(r2,c2))=self.boardView.detector.relevantRect(self.boardGrid.intersections[r][c],*(self.boardGrid.stoneSizeAt(r,c,sizeCoef))) + ((r1,c1),(r2,c2))=image_analyzer.relevantRect(self.boardGrid.intersections[r][c],*(self.boardGrid.stoneSizeAt(r,c,sizeCoef))) self.imgView.create_rectangle(r1,c1,r2,c2,outline="#00ffff") self.imgView.grid() - shift=EPoint(origWidth-self.img.width()*sizeCoef,origHeight-self.img.height()*sizeCoef)/2 - - self.boardView.redrawState(self.imgOrig,sizeCoef,shift) - ## Marks a point at the image with a green cross. Used for corners. def markPoint(self,x,y): self.imgView.create_line(x-3,y-3,x+4,y+4,fill="#00ff00") @@ -100,15 +113,14 @@ class Application(tk.Frame): self.imgView.grid() - def refreshTresholds(self): - self.boardView.detector.tresB=self.scaleTresB.get() - self.boardView.detector.tresW=self.scaleTresW.get() + def refreshTresholds(self,_): + self.parent.sendMsg("setTresholds",tuple(),{"tresB":self.scaleTresB.get(), "tresW":self.scaleTresW.get()}) ## Handles and presents the game state as detected by the program. class BoardView(tk.Canvas): def __init__(self, master=None): - self.detector=ImageAnalyzer() + # self.detector=ImageAnalyzer() self.boardGrid=None tk.Canvas.__init__(self, master) @@ -118,19 +130,26 @@ class BoardView(tk.Canvas): self.grid() - ## Redraws and reananalyzes the board view. - def redrawState(self,img,sizeCoef,shift): - self.create_rectangle(0,0,360,360,fill="#ffcc00") - self.drawGrid() - - self.detector.analyze(img,sizeCoef,shift) - - for r,row in enumerate(self.detector.board): + def redrawState(self,gameState): + for r,row in enumerate(gameState): for c,point in enumerate(row): self.drawStone(r,c,point) self.grid() + ## Redraws and reananalyzes the board view. + # def redrawState(self,img,sizeCoef,shift): + # self.create_rectangle(0,0,360,360,fill="#ffcc00") + # self.drawGrid() + # + # self.detector.analyze(img,sizeCoef,shift) + # + # for r,row in enumerate(self.detector.board): + # for c,point in enumerate(row): + # self.drawStone(r,c,point) + # + # self.grid() + def drawGrid(self): for i in range(19): self.create_line(18,18*(i+1),360-18,18*(i+1),fill="#000000") # rows @@ -158,12 +177,53 @@ class BoardView(tk.Canvas): c+=1 self.create_oval(c*18-9,r*18-9,c*18+9,r*18+9,fill=hexCode) - def setBoardGrid(self,boardGrid): - self.boardGrid=boardGrid - self.detector.setGrid(boardGrid) + +class GUI: + def __init__(self): + self.root = tk.Tk() + self.root.title("OneEye {0}.{1}.{2}".format(*config.misc.version)) + + self.outcomingQueue=None + self.outcomingEvent=None + + self.mainWindow = MainWindow(self,master=self.root) + self.root.bind("<>", lambda e: self.mainWindow.redrawImgView()) + self.root.bind("<>", lambda e: print("fired receiveState")) + + def __call__(self,incomingQueue,incomingEvent,outcomingQueue,outcomingEvent): + self.outcomingQueue=outcomingQueue + self.outcomingEvent=outcomingEvent + + self.listenerThread=threading.Thread(target=lambda: self._listen(incomingQueue,incomingEvent)) + self.listenerThread.start() + + self.mainWindow.mainloop() -root = tk.Tk() -root.title("OneEye {0}.{1}".format(*config.misc.version)) -app = Application(master=root) -app.mainloop() + def sendMsg(self,actionName,args=tuple(),kwargs=dict()): + self.outcomingQueue.put((actionName,args,kwargs)) + self.outcomingEvent.set() + + def _listen(self,incomingQueue,incomingEvent): + while True: + incomingEvent.wait() + msg=incomingQueue.get() + if incomingQueue.empty(): + incomingEvent.clear() + print(msg) + self._handleEvent(msg) + + def _handleEvent(self,e): + actions={"setCurrentFrame": self._frameHandler} + (actionName,args,kwargs)=e + + return actions[actionName](*args,**kwargs) + + def _frameHandler(self,newFrame,grid): + self.mainWindow.setCurrentFrame(newFrame,grid) + self.root.event_generate("<>") + + def _stateHandler(self,e): + pass + +gui=GUI() diff --git a/src/image_analyzer.py b/src/image_analyzer.py --- a/src/image_analyzer.py +++ b/src/image_analyzer.py @@ -1,5 +1,5 @@ import logging -from go import * +from go import Go class ImageAnalyzer: @@ -23,7 +23,7 @@ class ImageAnalyzer: def analyzePoint(self,image,row,col,imageCoords,stoneWidth,stoneHeight): b=w=e=0 - ((x1,y1),(x2,y2))=self.relevantRect(imageCoords,stoneWidth,stoneHeight) + ((x1,y1),(x2,y2))=relevantRect(imageCoords,stoneWidth,stoneHeight) for y in range(y1,y2+1): for x in range(x1,x2+1): @@ -42,13 +42,14 @@ class ImageAnalyzer: if w>=b and w>=e: return Go.WHITE return Go.EMPTY - def relevantRect(self,imageCoords,stoneWidth,stoneHeight): - x=int(imageCoords.x) - y=int(imageCoords.y) - xmax=max(int(stoneWidth*2//7), 2) # !! optimal parameters subject to further research - ymax=max(int(stoneHeight*2//7), 2) - - return ((x-xmax,y-ymax), (x+xmax,y+ymax)) - def setGrid(self,grid): self.grid=grid + + +def relevantRect(imageCoords,stoneWidth,stoneHeight): + x=int(imageCoords.x) + y=int(imageCoords.y) + xmax=max(int(stoneWidth*2//7), 2) # !! optimal parameters subject to further research + ymax=max(int(stoneHeight*2//7), 2) + + return ((x-xmax,y-ymax), (x+xmax,y+ymax))