'''
learning about contours in opencv
'''

# imports
###############################################################################
###############################################################################
from sklearn.ensemble import RandomForestClassifier
import numpy as np
from skimage.util import img_as_ubyte
from sklearn.metrics import confusion_matrix
import cv2
from skimage.feature import hog, corner_harris, corner_peaks
import skimage.measure as skim
from sklearn.model_selection import cross_val_score


# Random Forest Class
###############################################################################
###############################################################################
class MNIST_RF(object):
    
    
    data_file = r"C:\Users\jroberts\Documents\data\Digit Recognizer\train.csv"
    nr = 28
    nc = 28
    n_estimators = 100
    cv = 3
    
    
    def __init__(self):
        self.set_seed()
        self.rf = RandomForestClassifier(n_estimators=self.n_estimators)
    
    
    def set_seed(self,seed=0):
        np.random.seed(seed)


    def load(self):
        self.data = np.genfromtxt(open(self.data_file,'r'), delimiter=',', dtype='f8')[1:]   
        self.n = len(self.data)
    
    
    def init_features(self):
        self.labels = [x[0] for x in self.data]
        self.features = [x[1:] for x in self.data]
        self.names = ['image_pixels']*self.nr*self.nc

        
    def add_hog(self):
        for i in range(self.n):
            im0 = self.get_image(i)
            fd = hog(im0, orientations=9, pixels_per_cell=(7,7), cells_per_block=(2, 2), visualise=False)
            feat = np.append(self.features[i],fd)
            self.features[i] = feat
            
        for i in fd:
            self.names.append('hog_features')
        
        
    
    def add_contours(self):
        for i in range(self.n):
            
            # find the contours
            im0 = self.get_image(i)
            ret,im1 = cv2.threshold(im0,0,255,cv2.THRESH_OTSU)
            im2,contours,hierarchy = cv2.findContours(im1,cv2.RETR_LIST,cv2.CHAIN_APPROX_NONE)
            
            # add the number, area, and length of the contours as features
            feat = np.append(self.features[i],len(contours))
            clen = 0.
            carea = 0.
            for contour in contours:
                clen += cv2.arcLength(contour,True)
                carea += cv2.contourArea(contour)
            feat = np.append(feat,clen)
            feat = np.append(feat,carea)
            
            # add the moments for the longest contour
            tmp = [0]*len(contours)
            for i1,c in enumerate(contours):
                tmp[i1] = c.size
            moments = cv2.moments(contours[np.argmax(tmp)])
            for key,v in moments.iteritems():
                feat = np.append(feat,v)
            
            # add the hu moments for the longest contour
            hu = cv2.HuMoments(moments)
            hu = np.ndarray.tolist(hu.reshape(hu.size))
            for h in hu:
                feat = np.append(feat,h)
    
            # bounding box features
            (cx,cy),(width,height),ang = cv2.minAreaRect(contours[np.argmax(tmp)])
            feat = np.append(feat,cx)
            feat = np.append(feat,cy)
            feat = np.append(feat,width)
            feat = np.append(feat,height)
            feat = np.append(feat,height*width)
            feat = np.append(feat,ang)
            
            self.features[i] = feat
            
        # add all of the names of the features
        name = ['ncontours','contour_length','contour_area']
        for key,v in moments.iteritems():
            name.append('moment_features')
        for h in hu:
            name.append('hu_moment_features')
        name.append('bbox_cx')
        name.append('bbox_cy')
        name.append('bbox_width')
        name.append('bbox_height')
        name.append('bbox_area')
        name.append('bbox_angle')
        for n in name:
            self.names.append(n)
            
            
    def add_corners(self):
        for i in range(self.n):
            im0 = self.get_image(i)
            im1 = corner_harris(im0)
            feat = np.append(self.features[i],im1.reshape(im1.size))
            self.features[i] = feat
        
        for i in range(im1.size):
            self.names.append('corner_features')
            
            
    def add_regions(self):
        for i in range(self.n):
            im0 = self.get_image(i)
            ret,im1 = cv2.threshold(im0,0,255,cv2.THRESH_OTSU)
            limg = skim.label(1-im1,connectivity=1)
            feat = np.append(self.features[i],np.max(limg))
            self.features[i] = feat
            
        self.names.append('region_features')
        
            
    def add_eigenvalues(self):
        for i in range(self.n):
            im0 = self.get_image(i)
            ret,im1 = cv2.threshold(im0,0,255,cv2.THRESH_OTSU)
        
            a = np.unravel_index(np.where(im1>0),(self.nr,self.nc))
            tmp = np.zeros((len(a[1][0]),2))
            tmp[:,0] = a[1][1]
            tmp[:,1] = a[1][0]
            d,v = np.linalg.eig(np.cov(np.transpose(tmp - tmp.mean(axis=0)[np.newaxis,:])))
            dmin = np.min(d)
            dmax = np.max(d)
            feat = np.append(self.features[i],dmin)
            feat = np.append(feat,dmax)
            feat = np.append(feat,np.sqrt(1-dmin/dmax))
            self.features[i] = feat
            
        self.names.append('min_eigenval')
        self.names.append('max_eigenval')
        self.names.append('ratio_eigenval')
        
    
    def score(self):
        return np.mean(cross_val_score(self.rf,self.features,self.labels,cv=self.cv))
        
        
    def get_image(self,idx=0):
        return img_as_ubyte(np.uint8(a.data[idx,1:].reshape((a.nr,a.nc))))
        
        
    def remove_features(self,rmidx=None):
        idx = range(len(self.features[0]))
        keepidx = [i for i in idx if i not in rmidx]
        self.features = [tmp[keepidx] for tmp in self.features]
        self.names = [self.names[i] for i in keepidx]
        
        

###############################################################################
###############################################################################
if __name__=='__main__':
    
    a = MNIST_RF()
    a.load()
    
    a.init_features()
    print('Score after step 1: {0:6.4f}'.format(a.score()))
    
    a.add_hog()
    print('Score after step 2: {0:6.4f}'.format(a.score()))
    
    #a.remove_features(range(784))
    #print('Score after step 3: {0:6.4f}'.format(a.score()))

    a.add_contours()
    print('Score after step 4: {0:6.4f}'.format(a.score()))

    a.add_corners()
    print('Score after step 5: {0:6.4f}'.format(a.score()))

    a.add_regions()
    print('Score after step 6: {0:6.4f}'.format(a.score()))

    a.add_eigenvalues()
    print('Score after step 7: {0:6.4f}'.format(a.score()))




