# HG changeset patch
# User Laman
# Date 2016-11-23 22:18:22
# Node ID c842bba99503b483dd0a204b8746f3cd0b4bcc99
# Parent  fc8be31ce773e6503ecf8c4703860a731527ead4

rectangle representation of input intersections

diff --git a/.hgignore b/.hgignore
old mode 100644
new mode 100755
--- a/.hgignore
+++ b/.hgignore
@@ -1,2 +1,3 @@
 ^src/__pycache__/
-^\.idea/
\ No newline at end of file
+^\.idea/
+^images/
diff --git a/license.txt b/license.txt
old mode 100644
new mode 100755
diff --git a/project.md b/project.md
new file mode 100755
--- /dev/null
+++ b/project.md
@@ -0,0 +1,35 @@
+OneEye
+======
+
+Program to extract moves of a go game from video and save them or broadcast online.
+
+Modules:
+  - Video: grabbing still frames from a video file / stream. Using FFmpeg.
+  - Graphic: extracting game position from an image. Using Pillow.
+  - Watcher: interpreting sequence of game positions as a sequence of moves.
+  - Broadcaster: interfacing with a go server.
+
+
+Graphic
+-------
+Interpolating the board grid from specified corner points. Using vanishing points and horizon in projective geometry.
+
+Determining the point status based on majority voting of pixels in its neighbourhood (deciding by HSI intensity).
+
+Autodetection of the board?
+
+
+Watcher
+-------
+Base case: we have two correctly recognized positions differing by a single move (single added stone). Easy.
+
+Issues:
+  - illegal positions -> ignorable
+  - positions unreachable from the previous state
+    - reachable from any past state. (Incorrect states inbetween). How to pick the correct leaf of such a tree?
+    - reachable by more than one move. Issues with branching factor.
+  - board shifts -> repaired manually (or automatically), further positions have to be reevaluated
+  - stone shifts
+    - stone stops being recognized -> fixable manually and even ignorable
+    - stone is recognized at an empty intersection. It can be occupied later for real. What do?
+
diff --git a/readme.md b/readme.md
old mode 100644
new mode 100755
diff --git a/src/corners.py b/src/corners.py
old mode 100644
new mode 100755
diff --git a/src/epoint.py b/src/epoint.py
old mode 100644
new mode 100755
diff --git a/src/go.py b/src/go.py
old mode 100644
new mode 100755
diff --git a/src/grid.py b/src/grid.py
old mode 100644
new mode 100755
--- a/src/grid.py
+++ b/src/grid.py
@@ -60,6 +60,15 @@ 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):
+    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)
+
+    return (width,height)
 
 
 # from corners import Corners
diff --git a/src/gui.py b/src/gui.py
old mode 100644
new mode 100755
--- a/src/gui.py
+++ b/src/gui.py
@@ -1,13 +1,14 @@
 import tkinter as tk
 from PIL import ImageTk
 import PIL
-import math
-from epoint import *
 from corners import *
 from grid import *
 from image_analyzer import *
 from go import *
 
+showBigPoints=True
+showGrid=False
+
 
 class Application(tk.Frame):
   def __init__(self, master=None):
@@ -57,10 +58,18 @@ class Application(tk.Frame):
   ## Redraws the current image and its overlay.
   def redrawImgView(self):
     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()
+
     for corner in self.corners.corners:
       self.markPoint(corner.x,corner.y)
     
-    if self.boardGrid!=None:
+    if self.boardGrid!=None and showGrid:
       for r in range(19):
         a=self.boardGrid.intersections[r][0]
         b=self.boardGrid.intersections[r][-1]
@@ -69,16 +78,15 @@ class Application(tk.Frame):
         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 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)))
+          self.imgView.create_rectangle(r1,c1,r2,c2,outline="#00ffff")
     
     self.imgView.grid()
     
-    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()
-    
     shift=EPoint(origWidth-self.img.width()*sizeCoef,origHeight-self.img.height()*sizeCoef)/2
     
     self.boardView.redrawState(self.imgOrig,sizeCoef,shift,tresB,tresW)
@@ -91,7 +99,7 @@ class Application(tk.Frame):
     self.imgView.grid()
 
 
-## Handles and presents the game state as detected by the program.   
+## Handles and presents the game state as detected by the program.
 class BoardView(tk.Canvas):
   def __init__(self, master=None):
     self.detector=ImageAnalyzer()
@@ -99,7 +107,7 @@ class BoardView(tk.Canvas):
     
     tk.Canvas.__init__(self, master)
     self.configure(width=360,height=360,background="#ffcc00")
-    
+
     self.drawGrid()
     
     self.grid()
@@ -109,6 +117,9 @@ class BoardView(tk.Canvas):
   # @param tresB upper intensity treshold for a pixel to be considered black, [0-100]
   # @param tresW lower intensity treshold for a pixel to be considered white, [0-100]
   def redrawState(self,img,sizeCoef,shift,tresB,tresW):
+    self.create_rectangle(0,0,360,360,fill="#ffcc00")
+    self.drawGrid()
+
     self.detector.analyze(img,tresB,tresW,sizeCoef,shift)
     
     for r,row in enumerate(self.detector.board):
diff --git a/src/image_analyzer.py b/src/image_analyzer.py
old mode 100644
new mode 100755
--- a/src/image_analyzer.py
+++ b/src/image_analyzer.py
@@ -14,22 +14,16 @@ class ImageAnalyzer:
       for c in range(19):
         intersection=self.grid.intersections[r][c]
         
-        if c>0: stoneWidth=sizeCoef*(intersection.x-self.grid.intersections[r][c-1].x)
-        else: stoneWidth=sizeCoef*(self.grid.intersections[r][c+1].x-intersection.x)
-        if r>0: stoneHeight=sizeCoef*(intersection.y-self.grid.intersections[r-1][c].y)
-        else: stoneHeight=sizeCoef*(self.grid.intersections[r+1][c].y-intersection.y)
-        
-        self.board[r][c]=self.analyzePoint(image,intersection*sizeCoef+shift,stoneWidth,stoneHeight,tresB,tresW)
+        self.board[r][c]=self.analyzePoint(image,intersection*sizeCoef+shift,*(self.grid.stoneSizeAt(r,c,sizeCoef)),tresB,tresW)
   
   def analyzePoint(self,image,imageCoords,stoneWidth,stoneHeight,tresB,tresW):
     b=w=e=0
-    
-    cmax=max(int(stoneWidth*2//7),2) # !! optimal parameters subject to further research
-    rmax=max(int(stoneHeight*2//7),2)
-    
-    for r in range(-rmax,rmax+1):
-      for c in range(-cmax,cmax+1):
-        red,green,blue=image.getpixel((imageCoords.x+c,imageCoords.y+r))
+
+    ((x1,y1),(x2,y2))=self.relevantRect(imageCoords,stoneWidth,stoneHeight)
+
+    for y in range(y1,y2+1):
+      for x in range(x1,x2+1):
+        red,green,blue=image.getpixel((x,y))
         
         I=(red+green+blue)/255/3
         m=min(red,green,blue)
@@ -41,6 +35,14 @@ class ImageAnalyzer:
     if b>=w and b>=e: return Go.BLACK
     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