diff --git a/src/core.py b/src/core.py --- a/src/core.py +++ b/src/core.py @@ -1,49 +1,61 @@ import multiprocessing +import logging as log import PIL -from corners import Corners from gui import gui +from image_analyzer import ImageAnalyzer +from go import Go class Core: - corners=Corners() - tresW=60.0 - tresB=30.0 + def __init__(self): + self.grid=None + self.go=Go() + self.detector=ImageAnalyzer() + self.tresW=60.0 + self.tresB=30.0 - @staticmethod - def setCorners(corners): - Core.corners=corners + self._msgQueue=multiprocessing.Queue() + self._guiQueue=multiprocessing.Queue() + self._incomingEvent=multiprocessing.Event() + self._guiEvent=multiprocessing.Event() - @staticmethod - def setTresholds(tresB=None,tresW=None): - if tresB is not None: Core.tresB=tresB - if tresW is not None: Core.tresW=tresW + self._frame=PIL.Image.open("../images/7.jpg") + self._guiProc=multiprocessing.Process(name="gui", target=gui, args=(self._guiQueue,self._guiEvent,self._msgQueue,self._incomingEvent)) + self._guiProc.start() + self._guiQueue.put(("setCurrentFrame",(self._frame,),dict())) + self._guiEvent.set() -msgQueue=multiprocessing.Queue() -guiQueue=multiprocessing.Queue() -incomingEvent=multiprocessing.Event() -guiEvent=multiprocessing.Event() + def setCorners(self,corners): + self.detector.setGridCorners(corners) + self.detector.analyze(self._frame) + for r in self.detector.board: log.info(r) -frame=PIL.Image.open("../images/7.jpg") + def setTresholds(self,tresB=None,tresW=None): + if tresB is not None: self.tresB=tresB + if tresW is not None: self.tresW=tresW -guiProc=multiprocessing.Process(name="gui",target=gui,args=(guiQueue,guiEvent,msgQueue,incomingEvent)) -guiProc.start() -guiQueue.put(("setCurrentFrame",(frame,None),dict())) -guiEvent.set() + def listen(self): + while True: + self._incomingEvent.wait() + msg=self._msgQueue.get() + if self._msgQueue.empty(): + self._incomingEvent.clear() + log.info(msg) + self._handleEvent(msg) -while True: - incomingEvent.wait() - msg=msgQueue.get() - if msgQueue.empty(): - incomingEvent.clear() - print(msg) + def _handleEvent(self,e): + actions={"setCorners":self.setCorners, "setTresholds":self.setTresholds} + (actionName,args,kwargs)=e + return actions[actionName](*args,**kwargs) -# !! always pass object copies, don't share instances +core=Core() +core.listen() + """ core ==== -corners grid go imageAnalyzer @@ -51,6 +63,8 @@ imageAnalyzer gui === +corners + a) keeps references to important objects and uses them b) gets and sets all relevant data through method calls with core @@ -61,4 +75,7 @@ GUI BoardView -> redrawState(go) + + +core-gui: just pass messages with relevant data (!! always pass object copies, don't share instances) """ \ No newline at end of file diff --git a/src/go.py b/src/go.py --- a/src/go.py +++ b/src/go.py @@ -6,7 +6,7 @@ ## Initializes self.board to a list[r][c]=Go.EMPTY. def __init__(self,boardSize=19): self.boardSize=boardSize - self.board=[[Go.EMPTY]*self.boardSize for x in self.board] + self.board=[[Go.EMPTY]*self.boardSize for x in range(boardSize)] ## Executes a move. # diff --git a/src/grid.py b/src/grid.py --- a/src/grid.py +++ b/src/grid.py @@ -4,7 +4,7 @@ from epoint import EPoint ## Projective transformation of a point with a matrix A. # -# Takes a point as a horizontal vector and multiplies it transposed with A from left. +# Takes a point as a horizontal vector, transposes it and multiplies with A from left. # # @return transformed point as a numpy.array def transformPoint(point,A): @@ -21,11 +21,12 @@ class Grid: # # The result is stored in grid.intersections, a boardSize*boardSize list with [row][column] coordinates. # - # @param corners a properly initialized Corners object. !! Needs a check for the proper initialization. + # @param corners list of EPoints in ABCD order per corners.Corners.canonizeOrder(). + # !! Needs a check for proper initialization. def __init__(self,corners): # ad # bc - a,b,c,d=[c.toProjective() for c in corners.corners] + a,b,c,d=[c.toProjective() for c in corners] p1=numpy.cross(a,b) p2=numpy.cross(c,d) @@ -60,12 +61,12 @@ class Grid: affineIntersection=(rowStart*(boardSize-1-c)+rowEnd*c) / (boardSize-1) self.intersections[r][c]=EPoint.fromProjective(transformPoint(affineIntersection.toProjective(),rectiMatrixInv)) - def stoneSizeAt(self,r,c,sizeCoef): + def stoneSizeAt(self,r,c): intersection=self.intersections[r][c] - if c>0: width=sizeCoef*(intersection.x-self.intersections[r][c-1].x) - else: width=sizeCoef*(self.intersections[r][c+1].x-intersection.x) - if r>0: height=sizeCoef*(intersection.y-self.intersections[r-1][c].y) - else: height=sizeCoef*(self.intersections[r+1][c].y-intersection.y) + if c>0: width=intersection.x - self.intersections[r][c-1].x + else: width=self.intersections[r][c+1].x - intersection.x + if r>0: height=intersection.y - self.intersections[r-1][c].y + else: height=self.intersections[r+1][c].y - intersection.y return (width,height) diff --git a/src/gui/__init__.py b/src/gui/__init__.py --- a/src/gui/__init__.py +++ b/src/gui/__init__.py @@ -1,12 +1,15 @@ import threading +import logging as log import tkinter as tk from PIL import ImageTk import PIL import config +from epoint import EPoint from corners import Corners import image_analyzer from go import Go +from grid import Grid class MainWindow(tk.Frame): @@ -15,23 +18,31 @@ class MainWindow(tk.Frame): self.corners=Corners() self.currentFrame=None - self.boardGrid=None + self._boardGrid=None self.gameState=None self.img=None + self._imgSizeCoef=1 + self._imgShift=EPoint(0,0) tk.Frame.__init__(self, master) self.grid(column=0,row=0) self._createWidgets() - def setCurrentFrame(self,frame,grid): + def setCurrentFrame(self,frame): self.currentFrame=frame - self.boardGrid=grid w=int(self.imgView['width']) h=int(self.imgView['height']) self.img=ImageTk.PhotoImage(frame.resize((w,h),resample=PIL.Image.BILINEAR)) + wo,ho=self.currentFrame.size # o for original + widthRatio=wo/w + heightRatio=ho/h + self._imgSizeCoef=max(widthRatio,heightRatio) + # shift compensates possible horizontal or vertical empty margins from unmatching aspect ratios + self._imgShift=EPoint(wo-w*self._imgSizeCoef,ho-h*self._imgSizeCoef)/2 + def setGameState(self,gameState): pass @@ -67,7 +78,11 @@ class MainWindow(tk.Frame): def addCorner(self,x,y): self.corners.add(x,y) if self.corners.canonizeOrder(): - self.parent.sendMsg("setCorners",(self.corners,)) + # transform corners from show coordinates to real coordinates + log.debug(self.corners.corners) + self._boardGrid=Grid(self.corners.corners) + corners=[(c*self._imgSizeCoef+self._imgShift) for c in self.corners.corners] + self.parent.sendMsg("setCorners",(corners,)) # self.boardGrid=Grid(self.corners) # self.boardView.setBoardGrid(self.boardGrid) # @@ -78,30 +93,23 @@ class MainWindow(tk.Frame): if self.currentFrame and self.img: self.imgView.create_image(2,2,anchor="nw",image=self.img) - 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) - if self.boardGrid!=None and config.gui.showGrid: + if self._boardGrid!=None and config.gui.showGrid: for r in range(19): - a=self.boardGrid.intersections[r][0] - b=self.boardGrid.intersections[r][-1] + a=self._boardGrid.intersections[r][0] + b=self._boardGrid.intersections[r][-1] self.imgView.create_line(a.x,a.y,b.x,b.y,fill='#00ff00') for c in range(19): - a=self.boardGrid.intersections[0][c] - b=self.boardGrid.intersections[-1][c] + a=self._boardGrid.intersections[0][c] + b=self._boardGrid.intersections[-1][c] self.imgView.create_line(a.x,a.y,b.x,b.y,fill='#00ff00') - if self.boardGrid!=None and config.gui.showBigPoints: + if self._boardGrid!=None and config.gui.showBigPoints: for r in range(19): for c in range(19): - ((r1,c1),(r2,c2))=image_analyzer.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))) self.imgView.create_rectangle(r1,c1,r2,c2,outline="#00ffff") self.imgView.grid() @@ -121,7 +129,6 @@ class MainWindow(tk.Frame): class BoardView(tk.Canvas): def __init__(self, master=None): # self.detector=ImageAnalyzer() - self.boardGrid=None tk.Canvas.__init__(self, master) self.configure(width=360,height=360,background="#ffcc00") @@ -210,17 +217,17 @@ class GUI: msg=incomingQueue.get() if incomingQueue.empty(): incomingEvent.clear() - print(msg) + log.info(msg) self._handleEvent(msg) def _handleEvent(self,e): - actions={"setCurrentFrame": self._frameHandler} + actions={"setCurrentFrame":self._frameHandler} (actionName,args,kwargs)=e return actions[actionName](*args,**kwargs) - def _frameHandler(self,newFrame,grid): - self.mainWindow.setCurrentFrame(newFrame,grid) + def _frameHandler(self,newFrame): + self.mainWindow.setCurrentFrame(newFrame) self.root.event_generate("<>") def _stateHandler(self,e): diff --git a/src/image_analyzer.py b/src/image_analyzer.py --- a/src/image_analyzer.py +++ b/src/image_analyzer.py @@ -1,4 +1,5 @@ -import logging +import logging as log +from grid import Grid from go import Go @@ -11,14 +12,15 @@ class ImageAnalyzer: self.tresB=tresB self.tresW=tresW - def analyze(self,image,sizeCoef,shift): + # let's not concern ourselves with sizecoef and shift here anymore. we want corners to come already properly recomputed + def analyze(self,image): if self.grid==None: return False for r in range(19): for c in range(19): intersection=self.grid.intersections[r][c] - self.board[r][c]=self.analyzePoint(image,r,c,intersection*sizeCoef+shift,*(self.grid.stoneSizeAt(r,c,sizeCoef))) + self.board[r][c]=self.analyzePoint(image,r,c,intersection,*(self.grid.stoneSizeAt(r,c))) def analyzePoint(self,image,row,col,imageCoords,stoneWidth,stoneHeight): b=w=e=0 @@ -36,14 +38,14 @@ class ImageAnalyzer: elif 100*I>self.tresW: w+=1 else: e+=1 - logging.debug("(%d,%d) ... (b=%d,w=%d,e=%d)", row, col, b, w, e) + log.debug("(%d,%d) ... (b=%d,w=%d,e=%d)", row, col, b, w, e) if b>=w and b>=e: return Go.BLACK if w>=b and w>=e: return Go.WHITE return Go.EMPTY - def setGrid(self,grid): - self.grid=grid + def setGridCorners(self,corners): + self.grid=Grid(corners) def relevantRect(imageCoords,stoneWidth,stoneHeight):