def track_holes(self, image):
     rospy.loginfo('Tracking Fish')
     t = image.header.stamp.to_time()
     image = self.bridge.imgmsg_to_cv2(image, "bgr8")
     img = self.crop_img(image)
     angle = self.get_angle(t-self.start_time)
     fishes = self.rotate(self.fish_locales, angle)
     fish_found = 0
     for i in range(len(fishes)):
         if self.fish_holes[i].is_fish():
             location = Point(*fishes[i])
             if self.mouth_open(location):
                 location = location + self.board_center
                 cv2.circle(img,location.to_image(), 20,(0,255,0),2)
                 fish_found += 1
             else:
                 location = location + self.board_center
                 cv2.circle(img,location.to_image(), 20,(0,0,255),2)
             # rospy.loginfo('Fish At {}'.format(location))
         # for cam in self.openings:
         #     circle = cam + self.board_center
         #     cv2.circle(img, circle.to_image(),3,(0,255,0),2)
     if __name__ == "__main__":
         self.pub_results.publish(self.bridge.cv2_to_imgmsg(img, "bgr8"))
     else:
         return img
 def show_rotation(self, img, angle):
     fishes = self.rotate(self.fish_locales, angle)
     fish_found = 0
     for i in range(len(fishes)):
         if self.fish_holes[i].is_fish():
             location = Point(*fishes[i])
             if self.mouth_open(location):
                 location = location + self.board_center
                 cv2.circle(img,location.to_image(), 20,(0,255,0),2)
                 fish_found += 1
             else:
                 location = location + self.board_center
                 cv2.circle(img,location.to_image(), 20,(0,0,255),2)
     cv2.namedWindow("show_rotation")
     cv2.startWindowThread()
     cv2.imshow("show_rotation", img)
     cv2.waitKey(33)
    def getCircles(self, img, rad):
        img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

        circles = cv2.HoughCircles(img,cv.CV_HOUGH_GRADIENT,0.5,70,param1=10,
              param2=40, minRadius=15, maxRadius=35)

        circles = np.uint16(np.around(circles))
        
        rects = []
        for i in circles[0,:]: 
                 
            fish = Point(int(i[0]), int(i[1]), from_image=True)     
            cv2.circle(img,fish.to_image(),20,(0,255,0),2)      
            local = fish-self.board_center      
            rects.append(local.get_tuple())
        
        # self.pub_hough.publish(self.bridge.cv2_to_imgmsg(img, "8UC1"))
        return rects
class Calibrator(object):
    def __init__ (self):
        self.bridge = CvBridge()
        self.calibrated = False
        self.calibrating = False
        self.ready_for_cams = False

    def _init_ros(self):
        self.image_sub = rospy.Subscriber("/cv_camera/image_raw",Image,self.auto_calibrate, queue_size=1)


    def load_calibration(self):
        self.center_point = Point(*calibration_data.center_point)
        self.board_center = Point(*calibration_data.board_center)
        self.board_radius = calibration_data.board_radius
        self.fish = [Point(*a) for a in calibration_data.fish]
        self.cams = [Point(*c) for c in calibration_data.cams]
        self.base_cams = [Point(*c) for c in calibration_data.base_cams]
        self.skew_matrix = np.float32(calibration_data.skew_matrix)
        self.recalibrate = True


    def auto_calibrate(self, image):
        if not self.calibrating and not self.ready_for_cams:
            self.calibrating = True

            image = self.bridge.imgmsg_to_cv2(image, "bgr8")
            if self.recalibrate:
                color_space = [([170, 100, 55], [255, 175, 125]), ([0, 200, 200], [100, 240, 240])] #(blue,yellow) #yello:([0, 200, 200], [100, 240, 240])
                ratio = 1
                blue_lower, blue_upper = color_space[0]
                thresh = self.threshhold(blue_lower, blue_upper, image)
                
                # yellow_lower, yellow_upper = color_space[1]
                # yellow_thresh = self.threshhold(yellow_lower, yellow_upper, image)
                # yellow_blobs = self.get_blobs(yellow_thresh)
                # yel_len = [len(a) for a in yellow_blobs]
                # indx = yel_len.index(max(yel_len))
                # yellow = yellow_blobs[indx]
                # yell_centroid = self.get_center(*[Point(*a, from_image=True) for a in yellow])

                blobs = self.get_blobs(thresh)
                lengths = [len(a) for a in blobs]
                try:
                    indx = lengths.index(max(lengths))
                    blob = blobs[indx]
                    xmax = Point(*max(blob, key=lambda p: p[0]), from_image=True)
                    xmin = Point(*min(blob, key=lambda p: p[0]), from_image=True)
                    ymax = Point(*max(blob, key=lambda p: p[1]), from_image=True)
                    ymin = Point(*min(blob, key=lambda p: p[1]), from_image=True)
                    # center = self.get_center(*[Point(*b, from_image=True) for b in blob])
                    self.skew_points = [xmax, xmin, ymax, ymin]
                    

                    center = self.get_center(*self.skew_points)
                    self.center_point = center
                except:
                    skew_points = [Point(self.board_radius, 0, from_polar=True),
                                   Point(self.board_radius, np.pi/2, from_polar=True),
                                   Point(self.board_radius, np.pi, from_polar=True),
                                   Point(self.board_radius, np.pi*3/2, from_polar=True)]
                    self.skew_points = [p + self.board_center for p in skew_points]
                    pass

                
                self.check_points(image)
                self.check_center(image)

                self.board_center = self.center_point
                loc_points = [p-self.center_point for p in self.skew_points]
                xmax = max(loc_points, key=lambda p: p.x)
                xmin = min(loc_points, key=lambda p: p.x)
                radii = [p.magnitude() for p in [xmin, xmax]]
                theta = [p.angle() for p in loc_points]
                r = np.mean(radii)
                self.board_radius = r
                skewed = [Point(r, t, from_polar=True)+self.center_point for t in theta]
                before = np.float32([p.to_image() for p in self.skew_points])
                after = np.float32([p.to_image() for p in skewed])
                # self.center_point = self.get_center(*skewed)
                self.skew_matrix = cv2.getPerspectiveTransform(before, after)
                
                img = self.crop_img(image)

                y,x,c = img.shape
                self.center_point = Point(float(x)/2, float(y)/2, from_image=True)
                self.check_center(img)
                self.get_fish(img)
            self.get_cams(image)
            self.calibrated = True
        elif self.ready_for_cams and not self.calibrating:
            self.calibrating = True
            image = self.bridge.imgmsg_to_cv2(image, "bgr8")
            self.locate_cams(image)
            self.calibrated = True


    def check_points(self, image):
        cv2.namedWindow("Points")
        cv2.startWindowThread()
        cv2.setMouseCallback("Points", self._check_points)
        while True:
            img = copy.copy(image)
            for point in self.skew_points:
                cv2.circle(img, point.to_image(),3,(255,0,0),3)
            cv2.imshow('Points', img)
            k = cv2.waitKey(33)
            if k==10:
                if len(self.skew_points) == 4:
                    break
                else:
                    print 'Select only four points'
            elif k==-1:
                pass
            else:
                print k
                print 'Press Enter to continue..'
        cv2.destroyWindow("Points")

    def _check_points(self, event, x, y, flags, param):
        if event == cv2.EVENT_LBUTTONDOWN:
            if len(self.skew_points) < 4:
                self.skew_points.append(Point(x,y, from_image=True))
            else:
                print "Select only four points, right click to remove one"

        if event == cv2.EVENT_RBUTTONDOWN:
            target = Point(x,y,from_image=True)
            for point in self.skew_points:
                if (target-point).magnitude() < 10:
                    self.skew_points.remove(point)
    
    def get_fish(self, image):
        cv2.namedWindow("Fish")
        cv2.startWindowThread()
        cv2.setMouseCallback("Fish", self._get_fish)
        img = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        circles = cv2.HoughCircles(img,cv.CV_HOUGH_GRADIENT,0.5,70,param1=10,
              param2=40, minRadius=15, maxRadius=35)
        try:
            circles = np.uint16(np.around(circles))
            self.fish = []
            for i in circles[0,:]: 
                fish = Point(int(i[0]), int(i[1]), from_image=True)     
                local = fish-self.center_point      
                self.fish.append(local)
        except:
            pass
        
        while True:
            img = copy.copy(image)
            if len(self.fish) is not 0:
                for fish in self.fish:
                    location = fish + self.center_point
                    cv2.circle(img, location.to_image(),20,(0,255,0),3)
            cv2.imshow('Fish', img)
            k = cv2.waitKey(33)
            if k==10:
                if len(self.fish) < 21:
                    print 'Please select all 21 fish'
                else:
                    break
            elif k==-1:
                pass
            else:
                print k
                print 'Press Enter to continue..'
        cv2.destroyWindow('Fish')


    def _get_fish(self, event, x, y, flags, param):
        if event ==cv2.EVENT_LBUTTONDOWN:
            if len(self.fish) < 21:
                fish = Point(x,y,from_image=True)
                self.fish.append(fish-self.center_point)
            else:
                print 'Press enter to contiue or remove a fish to replace'
            
        elif event == cv2.EVENT_RBUTTONDOWN:
            target = Point(x,y,from_image=True)-self.center_point
            for fish in self.fish:
                if (target-fish).magnitude() < 25:
                    self.fish.remove(fish)


    def get_cams(self, image):
        cv2.namedWindow("Screw")
        cv2.startWindowThread()
        cv2.setMouseCallback("Screw", self._get_screw)
        self.screw_location = Point(0,0)
        while True:
            img = copy.copy(image)
            cv2.circle(img, self.screw_location.to_image(), 3, (0,0,255), 3)
            cv2.imshow('Screw', img)
            k = cv2.waitKey(33)
            if k==10:
                # enter pressed
                break
            elif k==-1:
                pass
            else:
                print k
                print 'Press Enter to continue..'
        cv2.destroyWindow('Screw')
        arc = self.screw_location - self.board_center
        cam_angle = arc.angle()
        self.cam_angle = cam_angle
        if len(self.base_cams) != 0:
            self.cams = self.rotate(self.base_cams, cam_angle)
            # self.cams = [c for c in self.cams if c.y <= 0]
            self.locate_cams(image)
            


    def _get_screw(self, event, x, y, flags, param):
        if event == cv2.EVENT_LBUTTONDOWN:
            self.screw_location = Point(x,y, from_image=True)
            
    
    def locate_cams(self, image):
        cv2.namedWindow("Cams")
        cv2.startWindowThread()
        cv2.setMouseCallback("Cams", self._locate_cams)
        img_cropped = self.crop_img(image)
        while True:
            img = copy.copy(img_cropped)
            for point in self.cams:
                location = point+self.center_point
                cv2.circle(img, location.to_image(),3,(255,0,0),3)
            cv2.imshow('Cams', img)
            k = cv2.waitKey(33)
            if k==10:
                break
                    
            elif k==-1:
                pass
            else:
                print k
                print 'Press Enter to continue..'
        cv2.destroyWindow("Cams")
        # self.base_cams = self.rotate(self.cams, -self.cam_angle)
        



    def _locate_cams(self, event, x, y, flags, param):
        if event == cv2.EVENT_LBUTTONDOWN:
            if len(self.cams) < 8:
                self.cams.append(Point(x,y, from_image=True)-self.center_point)
            else:
                print "Select only four points, right click to remove one"

        if event == cv2.EVENT_RBUTTONDOWN:
            target = Point(x,y,from_image=True)
            for point in self.cams:
                if (target-(point+self.center_point)).magnitude() < 10:
                    self.cams.remove(point)

    def rotate(self, l, theta):
        a = [p.get_tuple() for p in l]
        r = np.matrix([[np.cos(theta), np.sin(theta)],
                          [-np.sin(theta), np.cos(theta)]])
        x = np.matrix(a)
        rotated = x*r
        return [Point(*p) for p in rotated.tolist()]

    def threshhold(self, lower, upper, image):
        # create NumPy arrays from the boundaries
        lower = np.array(lower, dtype = "uint8")
        upper = np.array(upper, dtype = "uint8")
        # find the colors within the specified boundaries and apply
        # the mask
        mask = cv2.inRange(image, lower, upper)
        output = cv2.bitwise_and(image, image, mask = mask)
        # convert the resized image to grayscale, blur it slightly,
        # and threshold it
        gray = cv2.cvtColor(output, cv2.COLOR_BGR2GRAY)
        blurred = cv2.GaussianBlur(gray, (5, 5), 0)
        thresh = cv2.threshold(blurred, 60, 255, cv2.THRESH_BINARY)[1]
        return thresh


    def check_center(self, image):
        cv2.namedWindow("Center")
        cv2.startWindowThread()
        cv2.setMouseCallback("Center", self._check_center)
        while True:
            center = self.center_point
            img = copy.copy(image)
            if center is not 0:
                cv2.circle(img, center.to_image(),3,(255,0,0),3)
            cv2.imshow('Center', img)
            k = cv2.waitKey(33)
            if k==10:
                # enter pressed
                break
            elif k==-1:
                pass
            else:
                print k
                print 'Press Enter to continue..'
        cv2.destroyWindow('Center')


    def _check_center(self, event, x, y, flags, param):
        if event == cv2.EVENT_LBUTTONDOWN:
            self.center_point = Point(x,y, from_image=True)


    def get_center(self, *args):
        x = 0 
        y = 0 
        num = 0
        for arg in args:
            x += arg.x
            y += arg.y
            num += 1
        return Point(float(x)/num,float(y)/num)


    def get_blobs(self, img):
        blob_points = set()
        blobs = []
        blob_index = 0
        xmax = img.shape[1]
        ymax = img.shape[0]
        for y in range(ymax):
            for x in range(xmax):
                if (x,y) not in blob_points:
                    val = img[y,x]
                    if val != 0:
                        blob = set()
                        blob.add((x,y))
                        checked = set()
                        checked.add((x,y))
                        to_check = set()
                        i = x
                        j = y
                        neighbors = [(i-1,j),(i,j-1),(i-1,j-1),(i+1,j),(i,j+1),(i+1,j+1),(i-1,j+1),(i+1,j-1)]
                        neighbors = [n for n in neighbors if n[0] < xmax and n[1] < ymax and n[0] >= 0 and n[1] >= 0]
                        to_check.update([n for n in neighbors if n not in checked])
                        while len(to_check) > 0:
                            point = to_check.pop()
                            i,j = point
                            checked.add(point)
                            # print checked
                            val = img[point[1], point[0]]
                            if val != 0:
                                blob.add(point)
                                neighbors = [(i-1,j),(i,j-1),(i-1,j-1),(i+1,j),(i,j+1),(i+1,j+1),(i-1,j+1),(i+1,j-1)]
                                neighbors = [n for n in neighbors if n[0] < xmax and n[1] < ymax and n[0] >= 0 and n[1] >= 0]
                                to_check.update([n for n in neighbors if n not in checked])
                        blobs.append(blob)
                        blob_points.update(blob)
                        blob_index += 1
        return blobs


    def crop_img(self, image):
        img = cv2.warpPerspective(image, self.skew_matrix, (700, 700))
        y = -self.board_center.y
        x = self.board_center.x
        r = self.board_radius
        return image[y-r:y+r, x-r:x+r]

    def write(self, data):
        # center_point
        data.write('center_point = {} \n'.format(self.center_point.get_tuple()))
        # board_center
        data.write('board_center = {} \n'.format(self.board_center.get_tuple()))
        # board_radius
        data.write('board_radius = {} \n'.format(self.board_radius))
        data.write('board_edges = {} \n'.format([e.get_tuple() for e in self.skew_points]))
        # fish
        data.write('fish = {} \n'.format([f.get_tuple() for f in self.fish]))
        # cams
        data.write('base_cams = {} \n'.format([c.get_tuple() for c in self.base_cams]))
        data.write('cams = {} \n'.format([c.get_tuple() for c in self.cams if c.y <= 0]))
        # skew_matrix
        data.write('skew_matrix = {} \n'.format(str(self.skew_matrix.tolist())))
class HandFishFinder(object):
    def __init__(self):
        self.bridge = CvBridge()
        self.image_pub = rospy.Publisher("image_topic_2",Image, queue_size=2)
        self.image_sub = rospy.Subscriber("/cv_camera/image_raw",Image,self.callback)
        self.calibration_buffer = []
        self.buffer_full = False
        self.calibrated = False


    def add_image(self, image, t):
        
        buffer_len = len(self.calibration_buffer)
        if buffer_len == 0:
            self.calibration_buffer.append((t,image))
        elif buffer_len < BUFFER_SIZE:
            last = self.calibration_buffer[buffer_len-1][0]
            if t-last >= TIME_STEP:
                self.calibration_buffer.append((t,image))
        if len(self.calibration_buffer) == BUFFER_SIZE:
            self.buffer_full = True 

    def calibrate(self):
        while not self.buffer_full:
            time.sleep(0.5)
        cv2.namedWindow("Calibration")
        cv2.startWindowThread()
        cv2.setMouseCallback("Calibration", self.mouse_press)
        self.calibration_index = 0
        self.board_center = [(0,0)]*BUFFER_SIZE
        self.fish_center = [(0,0)]*BUFFER_SIZE
        t_steps = []
        for img in self.calibration_buffer:
            self.calibration_image = img[1]
            t_steps.append(img[0])
            self.calibration_phase = 'board'
            cv2.imshow('Calibration', img[1])
            k = cv2.waitKey(0)
            self.calibration_index += 1
        cv2.destroyWindow('Calibration')
        # Now that we have some data let's use it
        x = np.mean([point.x for point in self.board_center])
        y = np.mean([point.y for point in self.board_center])
        
        self.center_point = Point(x,y)
        self._calibrate(self.center_point, self.fish_center, t_steps)
        self.calibrated = True

    def _calibrate(self, board_center, fish_centers, t):
        """
        """
        self.board_center = board_center
        radii = []
        theta = []
        for point in fish_centers:
            pos = point-self.board_center
            radii.append(pos.magnitude())
            theta.append(pos.angle())

        self.radius = np.mean(radii)
        rates = []
        for i in range(1, len(t)-1):
            dTheta = theta[i]-theta[i-1]
            dt = t[i]-t[i-1]
            rates.append(dTheta/dt)
        rate = np.mean(rates)
        bs = []
        for i in range(0, len(t)-1):
            bs.append(theta[i] - rate*t[i])

        # freq = rate/(2*np.pi)
        # phase = np.arcsin((theta[0]-np.pi)/np.pi) - freq*t[0]
        b = np.mean(bs)
        # self.find_angle = lambda t_step: -(np.pi + np.pi*np.sin(freq*t_step+phase)) 
        self.find_angle = lambda t_step: rate*t_step + b
        self.calibrated = True
        

    def angle(self, x1,x2):
        return np.arctan2(x2,x1)


    def mouse_press(self, event, x, y, flags, param):
        if event == cv2.EVENT_LBUTTONDOWN:
            print 'Mouse event at {}'.format((x,y))
            self.board_center[self.calibration_index] = Point(x,y, from_image=True)
        elif event == cv2.EVENT_RBUTTONDOWN:
            self.fish_center[self.calibration_index] = Point(x,y, from_image=True)

    
    def callback(self, image):
        t = image.header.stamp.to_time()
        image = self.bridge.imgmsg_to_cv2(image, "bgr8")
        if not self.buffer_full:
            self.add_image(image, t)
        if self.calibrated:
            theta = self.find_angle(t)
            rospy.loginfo('Theta: {}'.format(theta))
            fish_pos = Point(self.radius, theta, from_polar=True)
            glob_fish_pos = fish_pos+self.center_point
            cv2.circle(image,self.center_point.to_image(),3,(255,0,0),3)
            cv2.circle(image,glob_fish_pos.to_image(), 30,(0,255,0),2)
        self.image_pub.publish(self.bridge.cv2_to_imgmsg(image, "bgr8"))