In [2]:
import logging
import math
import ast

class SudokuMatrix(object):
    """
    Build monochrome matrix of the sudoku board.
    You can add new scans to the matrix and then you can normalize the
    results. Each scan is an arc of measurements.
    """

    # The radius of the circle covered by the scanning device.
    k_scanner_radius_mm = 48
    # A map from coordinates to a list of measurements for that point in space.
    measurements = {}

    def add_scan(self, cx, cy, degrees, measures):
        """cx, cy center of the measuring device
            degrees the size of the arc measured
            measures the samples gathered while measuring (we assume these are
                uniformly distributed along the movement arc).
        """
        if len(measures) < 1:
            logging.exception("No measurements provided")
        for i in range(0, len(measures)):
            deg_moved = degrees * i / len(measures)
            angle = math.radians(degrees / 2 - deg_moved)
            a = self.k_scanner_radius_mm * math.cos(angle)
            b = self.k_scanner_radius_mm * math.sin(angle)
            px = cx - b
            py = cy + a
            # x axis sampling is twice as much as y axis
            p = (int(math.floor(px * 2)), int(math.floor(py)))
            if p not in self.measurements:
                self.measurements[p] = []
            self.measurements[p].append(measures[i])
            print(px, py, measures[i])
        print('Scan added')

    def to_str(self):
        return str(self.measurements)
        
    def dump_to_file(self, filename):
        with open(filename, 'w') as f:
            f.write(str(self.measurements))
            f.close()

    def load_from_file(self, filename):
        fstr = ""
        try:
            with open(filename, 'r') as f:
                fstr = f.read()
                f.close()
            self.measurements = ast.literal_eval(fstr)
        except Exception as e:
            print('Unable to load file:')
            print(str(e))

In [325]:
import numpy as np
import skimage.filter
from skimage.morphology import disk
from skimage.filter import rank

class SudokuBoardImage(object):
    """
    Represents a grayscale image of a sudoku board constructed from a SudokuMatrix.    
    """
    
    def __init__(self, file_name):
        self.load_scan(file_name)
        self.image = self.build_image(self.m)
    
    def load_scan(self, file_name):
        self.m = SudokuMatrix()
        self.m.load_from_file('/home/ch/ev3dev/share/ev3-repo/' + file_name)        
    
    def build_image(self, m):
        """
          Convert the measurements in a SudokuMatrix into a numpy array.
        """
        a = m.measurements
        aav = dict()
        minx = 1 << 20
        maxx = - (1 << 20)
        miny = 1 << 20
        maxy = - (1 << 20)

        for k, v in a.iteritems():
            aav[k] = sum(v)/len(v)
            minx = min(minx, k[0])
            maxx = max(maxx, k[0])
            miny = min(miny, k[1])
            maxy = max(maxy, k[1])

        arr = []
        for i in range(minx, maxx):
            arr.append([])
            for j in range(miny, maxy):
                arr[i - minx].append(aav.get((i, j), 0))
        # We need to rotate the array and trim the top and bottom.    
        return np.rot90(np.array(arr)[:,15:-10])
        
    def edges(self):        
        thresh = rank.otsu(self.image.astype(np.uint8), disk(5))
        return self.image < thresh * 0.7
    
    @property
    def img(self):
        return self.image

    def dedupe_digit_rectangles(self, coords, size):
        """
        Uses a greedy approach to remove all overlapping digit rectangles
        Assumes the coordinates are sorted by row and then by col
        """
        dc = []

        for (i, j) in coords:
            intersects = False
            for (x, y) in dc:
                if abs(y - j) < size and abs(x - i) < size:
                    intersects = True
                    break
            if intersects:
                continue
            dc.append((i, j))
        return dc

    def digit_coordinates(self):
        """
        Return a set of coordinates that are the most likely lower right corners of a digit
        sum_h and sum_v not used for now.
        """
        k_size = 16
        k_perim_thr = .04
        k_area_thr = .08

        edges = self.edges()
        sum_h = np.zeros(edges.shape)
        sum_v = np.zeros(edges.shape)
        sum_all = np.zeros(edges.shape)

        for (i, j), value in np.ndenumerate(edges):
            # Compute the cumulative sums
            if i == 0 and j == 0:
                sum_h[i, j] = sum_v[i, j] = sum_all[i, j] = edges[i, j]
            elif i == 0:
                sum_h[i, j] = sum_all[i, j] = sum_h[i, j - 1] + edges[i, j]
            elif j == 0:
                sum_v[i, j] = sum_all[i, j] = sum_v[i - 1, j] + edges[i, j]
            else:
                sum_h[i, j] = sum_h[i, j - 1] + edges[i, j]
                sum_v[i, j] = sum_v[i - 1, j] + edges[i, j]
                sum_all[i, j] = edges[i, j] + sum_all[i - 1, j] + sum_all[i, j- 1] - sum_all[i - 1, j - 1]

        def sq_sum(m, i, j, size):
            # Computes the sum of a subsquare in a matrix with the end in i, j
            # m should be a matrix of cumulative sums.

            return (m[i, j] - 
                    (m[i, j - size] if j - size >= 0 else 0) -
                    (m[i - size, j] if i - size >= 0 else 0) +
                    (m[i - size, j - size] if i - size >= 0 and j - size >= 0 else 0))

        i = j = k_size - 1
        corners = []
        # We know digits don't overlap so when we find a digit we can skip
        # some rows and columns.
        found = False 

        while i < edges.shape[0] and j < edges.shape[1]:
            perim_and_inside = sq_sum(sum_all, i, j, k_size)
            inside = sq_sum(sum_all, i - 1, j - 1, k_size - 2)
            perim = perim_and_inside - inside

            if perim < (k_size * 4 - 4) * k_perim_thr:
                if inside > (k_size * k_size) * k_area_thr:
                    corners.append((i, j))            
                    # j += k_size - 1
                    found = True

            j += 1
            if j >= edges.shape[1]:
                j = k_size - 1
                i += 1 #(1 if not found else k_size)
                
        return self.dedupe_digit_rectangles(corners, k_size)

    def digit_coordinates_img(self):
        coord = self.digit_coordinates()
        
        res = np.zeros(self.image.shape)
        for (i, j) in coord:
            for k in xrange(10):
                res[i, j -  k] = res[i - k, j] = 1
                
        return res
    
def center_of_mass(matrix):
    total = 0.0
    mx = 0.0
    my = 0.0
    for i in xrange(0, len(matrix)):
        for j in xrange(0, len(matrix[i])):
            total += matrix[i][j]
            mx += matrix[i][j] * i
            my += matrix[i][j] * j
    return [mx / total, my / total]

def distance(p1, p2):
    return math.sqrt((p1[0] - p2[0]) * (p1[0] - p2[0]) + (p1[1] - p2[1]) * (p1[1] - p2[1]))
            
def center_of_mass_filter(image):
    k_kernel_size = 8
    res = np.zeros(image.shape)
    
    for i in xrange(k_kernel_size, len(image) - k_kernel_size + 1):
        for j in xrange(k_kernel_size, len(image[i]) - k_kernel_size + 1):
            cm = center_of_mass(image[i - k_kernel_size: i + k_kernel_size, j - k_kernel_size: j + k_kernel_size])
            d1 = abs(cm[0] - k_kernel_size)
            d2 = abs(cm[1] - k_kernel_size)
            res[i][j] = 100 if d1 < 0.2 and d2 < 0.2 else 0 
    return res

In [326]:
left = SudokuBoardImage('slim_left.txt')
center = SudokuBoardImage('slim_center.txt')
right = SudokuBoardImage('slim_right.txt')

In [116]:
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline

In [117]:
plt.subplot(1, 3, 1)
plt.imshow(left.img, cmap=plt.cm.gray, interpolation='none')
plt.subplot(1, 3, 2)
plt.imshow(center.img, cmap=plt.cm.gray, interpolation='none')
plt.subplot(1, 3, 3)
plt.imshow(right.img, cmap=plt.cm.gray, interpolation='none')
plt.show()



In [179]:
#plt.imshow(right.edges(), cmap=plt.cm.gray, interpolation='none')
im = center.img
#im = skimage.filter.sobel(im.astype(np.float64))
thresh = skimage.filter.threshold_otsu(im.astype(np.float64))
plt.imshow(im < thresh * 0.6, cmap=plt.cm.gray, interpolation='none')
# plt.imshow(im, cmap=plt.cm.gray, interpolation='none')


Out[179]:
<matplotlib.image.AxesImage at 0x7febcdb70290>

In [264]:
from skimage import data
from skimage.morphology import disk
from skimage.filter import threshold_otsu, rank
from skimage.util import img_as_ubyte

img = center.img.astype(np.uint8)
#img = enhance_contrast(bilat, disk(3))
local_otsu = rank.otsu(img, disk(5))
plt.imshow(center.img <= local_otsu * 0.7, cmap=plt.cm.gray, interpolation='none')


Out[264]:
<matplotlib.image.AxesImage at 0x7febc9e5c090>

In [250]:
from skimage.filter.rank import mean_bilateral, enhance_contrast
bilat = mean_bilateral(center.img.astype(np.uint16), disk(16), s0=8, s1=8)
bilat = enhance_contrast(bilat, disk(3))
plt.imshow(bilat, cmap=plt.cm.gray, interpolation='none')


Out[250]:
<matplotlib.image.AxesImage at 0x7febca85b390>

In [328]:
def pc(l):
    plt.imshow(edges[l[0] - 15:l[0], l[1] - 15:l[1]], cmap=plt.cm.gray, interpolation='none')

plt.subplot(1, 2, 1)
plt.imshow(center.edges(), cmap=plt.cm.gray, interpolation='none')
plt.subplot(1, 2, 2)
plt.imshow(center.digit_coordinates_img(), cmap=plt.cm.gray, interpolation='none')


Out[328]:
<matplotlib.image.AxesImage at 0x7febc2b3e610>

In [317]:
len(dedupe_digit_rectangles(right.digit_coordinates(), 16))


Out[317]:
9

In [232]:
# Author: Gael Varoquaux <gael dot varoquaux at normalesup dot org>
# License: BSD 3 clause

# Standard scientific Python imports
import matplotlib.pyplot as plt

# Import datasets, classifiers and performance metrics
from sklearn import datasets, svm, metrics
from sklearn.ensemble import GradientBoostingClassifier

# The digits dataset
digits = datasets.load_digits()

# The data that we are interested in is made of 8x8 images of digits, let's
# have a look at the first 3 images, stored in the `images` attribute of the
# dataset.  If we were working from image files, we could load them using
# pylab.imread.  Note that each image must have the same size. For these
# images, we know which digit they represent: it is given in the 'target' of
# the dataset.
def dou(matrix):
    dr = []
    for row in matrix:
        q = []
        for j in row:
            q += [j, j]        
        dr += [q, q]
    return dr

images_and_labels = list(zip(digits.images, digits.target))
for index, (image, label) in enumerate(images_and_labels[:4]):
    plt.subplot(2, 4, index + 1)
    plt.axis('off')
    plt.imshow(dou(image), cmap=plt.cm.gray_r, interpolation='none')
    plt.title('Training: %i' % label)

# To apply a classifier on this data, we need to flatten the image, to
# turn the data in a (samples, feature) matrix:
n_samples = len(digits.images)

# Resize the images
big_images = np.asarray([skf.threshold_adaptive(np.asarray(dou(im)), 16).astype(np.int32) for im in digits.images])
data = big_images.reshape((n_samples, -1))

# Create a classifier: a support vector classifier
classifier = svm.SVC(gamma=0.001)

# We learn the digits on the first half of the digits
classifier.fit(data[:n_samples / 2], digits.target[:n_samples / 2])

# Now predict the value of the digit on the second half:
expected = digits.target[n_samples / 2:]
predicted = classifier.predict(data[n_samples / 2:])

print("Classification report for classifier %s:\n%s\n"
      % (classifier, metrics.classification_report(expected, predicted)))
print("Confusion matrix:\n%s" % metrics.confusion_matrix(expected, predicted))

images_and_predictions = list(zip(digits.images[n_samples / 2:], predicted))
for index, (image, prediction) in enumerate(images_and_predictions[:4]):
    plt.subplot(2, 4, index + 5)
    plt.axis('off')
    plt.imshow(image, cmap=plt.cm.gray_r, interpolation='nearest')
    plt.title('Prediction: %i' % prediction)

plt.show()


Classification report for classifier SVC(C=1.0, cache_size=200, class_weight=None, coef0=0.0, degree=3,
  gamma=0.001, kernel='rbf', max_iter=-1, probability=False,
  random_state=None, shrinking=True, tol=0.001, verbose=False):
             precision    recall  f1-score   support

          0       0.97      0.98      0.97        88
          1       0.93      0.85      0.89        91
          2       0.87      0.87      0.87        86
          3       0.87      0.85      0.86        91
          4       0.99      0.92      0.96        92
          5       0.85      0.87      0.86        91
          6       0.97      0.98      0.97        91
          7       0.89      0.94      0.92        89
          8       0.92      0.74      0.82        88
          9       0.72      0.91      0.81        92

avg / total       0.90      0.89      0.89       899


Confusion matrix:
[[86  0  0  0  0  1  0  0  1  0]
 [ 0 77  6  1  0  0  1  0  1  5]
 [ 1  0 75  6  0  0  0  0  1  3]
 [ 0  1  1 77  0  3  0  4  1  4]
 [ 2  1  0  0 85  1  0  2  1  0]
 [ 0  0  0  0  0 79  1  0  0 11]
 [ 0  1  1  0  0  0 89  0  0  0]
 [ 0  1  1  0  1  2  0 84  0  0]
 [ 0  2  2  3  0  4  1  2 65  9]
 [ 0  0  0  2  0  3  0  2  1 84]]

In [170]:
print np.max(digits.images)
seven_normalized = (3 - ((seven - np.min(seven)) * 4 / (np.max(seven) - np.min(seven)))) * 4
print classifier.predict(np.asarray(dou(digits.images[17])).flatten())
print classifier.predict(seven_normalized.astype(np.float64).flatten())


16.0
[7]
[8]

In [171]:
seven_normalized


Out[171]:
array([[ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0],
       [ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0],
       [ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0],
       [ 0,  0,  0,  0,  4,  0,  4,  4,  8,  4,  8,  4,  4,  4,  0,  0],
       [ 0,  0,  0,  4,  8,  8, 12,  8, 12, 12,  8,  8,  4,  4,  0,  0],
       [ 0,  0,  4,  8,  8,  8,  8,  8,  8,  8, 12, 12,  4,  0,  0,  0],
       [ 0,  0,  0,  4,  0,  0,  4,  8,  8, 12, 12, 12,  4,  0,  0,  0],
       [ 0,  0,  0,  0,  0,  0,  0,  4, 12, 12,  8,  4,  0,  0,  0,  0],
       [ 0,  0,  0,  0,  0,  0,  4,  8, 12, 12,  8,  4,  0,  0,  0,  0],
       [ 0,  0, -4,  0,  0,  0, 12, 12, 12, 12,  4,  4,  0,  0,  0,  0],
       [ 0,  0,  0,  0,  4,  4, 12, 12, 12,  8,  4,  0,  0,  0,  0,  0],
       [ 0,  0,  0,  0,  8,  8, 12, 12,  8,  4,  0,  0,  0,  0,  0,  0],
       [ 0,  0,  0,  4,  8, 12, 12, 12,  4,  0,  0,  0,  0,  0,  0,  0],
       [ 0,  0,  0,  4,  4,  8,  4,  4,  0,  0,  0,  0,  0,  0,  0,  0],
       [ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0],
       [ 0,  0,  0,  0,  4,  0,  4,  0,  4,  0,  4,  0,  4,  4,  0,  0]])

In [168]:
np.asarray(dou(digits.images[17])).astype(np.int32)


Out[168]:
array([[ 0,  0,  0,  0,  1,  1,  8,  8, 15, 15, 10, 10,  0,  0,  0,  0],
       [ 0,  0,  0,  0,  1,  1,  8,  8, 15, 15, 10, 10,  0,  0,  0,  0],
       [ 0,  0,  3,  3, 13, 13, 15, 15, 14, 14, 14, 14,  0,  0,  0,  0],
       [ 0,  0,  3,  3, 13, 13, 15, 15, 14, 14, 14, 14,  0,  0,  0,  0],
       [ 0,  0,  5,  5, 10, 10,  0,  0, 10, 10, 12, 12,  0,  0,  0,  0],
       [ 0,  0,  5,  5, 10, 10,  0,  0, 10, 10, 12, 12,  0,  0,  0,  0],
       [ 0,  0,  0,  0,  3,  3,  5,  5, 15, 15, 10, 10,  2,  2,  0,  0],
       [ 0,  0,  0,  0,  3,  3,  5,  5, 15, 15, 10, 10,  2,  2,  0,  0],
       [ 0,  0,  0,  0, 16, 16, 16, 16, 16, 16, 16, 16, 12, 12,  0,  0],
       [ 0,  0,  0,  0, 16, 16, 16, 16, 16, 16, 16, 16, 12, 12,  0,  0],
       [ 0,  0,  1,  1,  8,  8, 12, 12, 14, 14,  8,  8,  3,  3,  0,  0],
       [ 0,  0,  1,  1,  8,  8, 12, 12, 14, 14,  8,  8,  3,  3,  0,  0],
       [ 0,  0,  0,  0,  0,  0, 10, 10, 13, 13,  0,  0,  0,  0,  0,  0],
       [ 0,  0,  0,  0,  0,  0, 10, 10, 13, 13,  0,  0,  0,  0,  0,  0],
       [ 0,  0,  0,  0,  0,  0, 11, 11,  9,  9,  0,  0,  0,  0,  0,  0],
       [ 0,  0,  0,  0,  0,  0, 11, 11,  9,  9,  0,  0,  0,  0,  0,  0]], dtype=int32)

In [172]:
for index, (image, label) in enumerate(images_and_labels[16:20]):
    plt.subplot(2, 4, index + 1)
    plt.axis('off')
    plt.imshow(dou(seven_normalized), cmap=plt.cm.gray_r, interpolation='nearest')
    plt.title('Training: %i' % label)



In [251]:
from sklearn.ensemble import GradientBoostingClassifier
# BDT
classifier_BDT = GradientBoostingClassifier(n_estimators=100, learning_rate=1.0, max_depth=12, random_state=3)

# We learn the digits on the first half of the digits
classifier_BDT.fit(data[:n_samples / 2], digits.target[:n_samples / 2])

# Now predict the value of the digit on the second half:
expected = digits.target[n_samples / 2:]
predicted = classifier_BDT.predict(data[n_samples / 2:])

print("Classification report for classifier %s:\n%s\n"
      % (classifier_BDT, metrics.classification_report(expected, predicted)))
print("Confusion matrix:\n%s" % metrics.confusion_matrix(expected, predicted))


Classification report for classifier GradientBoostingClassifier(init=None, learning_rate=1.0, loss='deviance',
              max_depth=12, max_features=None, max_leaf_nodes=None,
              min_samples_leaf=1, min_samples_split=2, n_estimators=100,
              random_state=3, subsample=1.0, verbose=0, warm_start=False):
             precision    recall  f1-score   support

          0       0.94      0.97      0.96        88
          1       0.90      0.82      0.86        91
          2       0.85      0.90      0.87        86
          3       0.84      0.74      0.78        91
          4       0.96      0.71      0.81        92
          5       0.89      0.88      0.88        91
          6       0.96      0.97      0.96        91
          7       0.90      0.90      0.90        89
          8       0.77      0.61      0.68        88
          9       0.56      0.89      0.69        92

avg / total       0.86      0.84      0.84       899


Confusion matrix:
[[85  0  2  0  0  1  0  0  0  0]
 [ 0 75  2  2  1  0  0  0  3  8]
 [ 1  1 77  1  0  1  0  2  1  2]
 [ 0  3  5 67  0  1  0  2  3 10]
 [ 4  0  0  0 65  0  1  3  3 16]
 [ 0  0  0  0  2 80  2  0  0  7]
 [ 0  1  0  0  0  0 88  0  1  1]
 [ 0  0  0  0  0  1  1 80  3  4]
 [ 0  3  5  7  0  2  0  1 54 16]
 [ 0  0  0  3  0  4  0  1  2 82]]

In [253]:
print np.max(digits.images)
seven_normalized = (3 - ((seven - np.min(seven)) * 4 / (np.max(seven) - np.min(seven)))) * 4
seven_t = 1 - skf.threshold_adaptive(seven.astype(np.float64), 16)
six_t = 1 - skf.threshold_adaptive(six.astype(np.float64), 16)
print classifier_BDT.predict(np.asarray(dou(digits.images[17])).flatten())
print classifier_BDT.predict(seven_t.flatten())


16.0
[4]
[8]

In [82]:
mo = one - np.min(one)
len((mo.astype(np.float64) / np.max(mo) * 2 - 1).flatten())


Out[82]:
196

In [5]:
import os


Out[5]:
<module 'posixpath' from '/home/ch/anaconda/lib/python2.7/posixpath.pyc'>

In [59]:
#### Detect where digits are.
np.max(arr_right)


Out[59]:
85

In [39]:
for (i, j) in zip(range(2) * range(3)):
    print i, j


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-39-77317d2cbbf8> in <module>()
----> 1 for (i, j) in zip(range(2) * range(3)):
      2     print i, j

TypeError: can't multiply sequence by non-int of type 'list'

In [59]:



  File "<ipython-input-59-8a0cfb388cea>", line 2
    
    ^
IndentationError: expected an indented block

In [ ]: