def test_contains_point_returns_true_if_the_specified_point_is_within_radius(
         self):
     circle = Circle(Point(0, 0), 10)
     point_center = Point(0, 0)
     self.assertTrue(circle.contains_point(point_center))
     point_middle = Point(5, 5)
     self.assertTrue(circle.contains_point(point_middle))
Exemple #2
0
    def _calculate_square_and_writing_metric(self, binary_image, transform, size):
        """ Similar to the _calculate_square_metric function above, but also includes the regions above
        and below that barcode that contain a text version of the information contained in the barcode.
        This may do a better job of correctly fitting the square in certain circumstances, but may also
        get trapped if there are dark/shadow regions around the edge of the image. """
        center = transform.trans
        angle = transform.rot

        rotated = binary_image.rotate(angle, center)
        brightness = rotated.calculate_brightness(center, size, size) * size**2

        txt_height = self.TXT_HEIGHT * size
        txt_width = self.TXT_WIDTH * size
        area = txt_width * txt_height

        rect1_center = center + Point(0, self.TXT_OFFSET*size)
        rect2_center = center - Point(0, self.TXT_OFFSET*size)

        brightness += rotated.calculate_brightness(rect1_center, txt_width, txt_height) * area
        brightness += rotated.calculate_brightness(rect2_center, txt_width, txt_height) * area

        brightness /= (size**2 + 2*area)

        self.count += 1
        return brightness
 def test_scale_returns_new_circle_which_is_a_scaled_version_of_input_one(
         self):
     circle = Circle(Point(3, 0), 10)
     new_circle = circle.offset(Point(3, 3))
     self.assertEqual(new_circle.x(), 6)
     self.assertEqual(new_circle.y(), 3)
     self.assertEqual(new_circle.radius(), 10)
Exemple #4
0
    def find_location(self):
        # find center, radius and location of the puck
        # use feature detection to identify the orientation of the puck
        self.unipuck_contours = self._find_puck_contours()
        (x, y), radius = self._find_enclosing_circle_of_largest_contour()
        features_cnt = self._find_contours_of_features(x, y, radius)
        match_factor, match_cnt = self._find_feature(features_cnt)

        uni = None

        if (match_factor < FEATURE_MATCH_FACTOR):  # take only the very good feature matches
            hull = cv2.convexHull(match_cnt)
            hull_area = cv2.contourArea(hull)
            feature_area = cv2.contourArea(match_cnt)
            #puck_area = math.pi * math.sqrt(radius)
            area_factor = feature_area / hull_area
            #puck_area_factor = feature_area / puck_area
            if round(area_factor, 2) > FEATURE_HULL_MATCH_FACTOR:# and puck_area_factor > PUCK_FEATURE_AREA_FACTOR_MIN:
                # take the features which have only small convexity defects

                (cx, cy) = self._find_contour_momentum(match_cnt)

                feature_orientation = math.atan2(cy - y, cx - x) # feature orientation
                puck_orientation = feature_orientation - math.pi / 2  # puck orientation shifted -pi/2 rad from feature orientation
                uni = Unipuck(Point(x, y), radius)
                uni.set_rotation(puck_orientation)
                uni.set_feature_center(Point(cx, cy))
                uni.set_feature_boarder(match_cnt)

        return uni
def TRIANGLE_DEMO():
    """ Draw a set of axes and a triangle. Perform a series of random transformations
     on the triangle and display the results.
    """
    A = Point(143, 52)
    B = Point(17, 96.5)
    C = Point(0, 0)

    for i in range(10):
        # Create random transformation
        angle = random.random() * 2 * math.pi
        scale = random.random() * 3
        delX = (random.random() - 0.5) * 200
        delY = (random.random() - 0.5) * 200
        translate = Point(delX, delY)
        transform = Transform(translate, angle, scale)

        # Transform the triangle
        A_ = transform.transform(A)
        B_ = transform.transform(B)
        C_ = transform.transform(C)

        # From the line A-B and the transformed line A'-B', determine what the transformation was
        # This should be the same as the original transformation
        trans_calc = Transform.line_mapping(A, B, A_, B_)
        print("Angle: {:.2f}; {:.2f}".format(rad_to_deg(angle), rad_to_deg(trans_calc.rot)))
        print("Trans: ({}); ({})".format(translate, trans_calc.trans))
        print("Zoom: {:.2f}; {:.2f}".format(scale, trans_calc.zoom))

        # Display on image
        image = Image.blank(1000, 800)
        image.draw_offset = IMG_CENTER
        draw_axes(image, 300)

        # Draw original triangle
        image.draw_line(A, B, Color.Red(), 5)
        image.draw_line(C, B, Color.Red(), 5)
        image.draw_line(A, C, Color.Red(), 5)

        #Draw transformed triangle
        image.draw_line(A_, B_, Color.Green())
        image.draw_line(A_, C_, Color.Green())
        image.draw_line(C_, B_, Color.Green())

        # Check that the reverse transformation works properly
        A__ = transform.reverse(A_)
        B__ = transform.reverse(B_)
        C__ = transform.reverse(C_)

        # Draw the reverse transformation - this should overlap the origianl triangle
        image.draw_line(A__, B__, Color.Green(), 1)
        image.draw_line(A__, C__, Color.Green(), 1)
        image.draw_line(C__, B__, Color.Green(), 1)

        # Write the transformation on the image
        image.draw_text(transform.__str__(), Point(-450, 350), Color.White(), centered=False, scale=0.5, thickness=1)

        # Show the image
        image.popup()
Exemple #6
0
def edges_image(edge_sets):
    blank = Image.blank(image_original.width, image_original.height, 3, 255)

    for shape in edge_sets:
        for edge in shape:
            print(edge[1])
            blank.draw_line(Point.from_array(edge[0]), Point.from_array(edge[1]), Color.Green(), 1)

    return blank
def draw_axes(img, length):
    # Draw axes
    x_neg = Point(-length, 0)
    x_pos = Point(length, 0)
    y_neg = Point(0, -length)
    y_pos = Point(0, length)

    img.draw_line(y_neg, y_pos, Color.White(), 5)
    img.draw_line(x_neg, x_pos, Color.White(), 5)
Exemple #8
0
 def draw_rectangle(self, roi, color, thickness=2):
     """ Draw the specified rectangle on the image (in place). """
     top_left = self._format_point(Point(roi[0], roi[1]))
     bottom_right = self._format_point(Point(roi[2], roi[3]))
     cv2.rectangle(self.img,
                   top_left.tuple(),
                   bottom_right.tuple(),
                   color.bgra(),
                   thickness=thickness)
    def _find_puck_center(pin_centers):
        """ Calculate approximate center point of the puck from positions of the center points
        of the pin slots.

        Within each layer there may be some missing points, so if we calculate the center
        position of the puck by averaging the center positions of the slots, the results will
        be a bit out. Instead, we use the average center position (the centroid) as a starting
        point and divide the slots into two groups based on how close they are to the centroid.
        As long as not too many slots are missing, the division into groups should work well.
        We then iterate over different values for the puck center position, attempting to find
        a location that is equidistant from all of the slot centers.
        """
        centroid = calculate_centroid(pin_centers)

        # Calculate distance from center to each pin-center
        distances = [[point, point.distance_to(centroid)]
                     for point in pin_centers]
        distances = sorted(distances, key=lambda distance: distance[1])

        # Sort the points into two layers based on their distance from the centroid
        layer_break = _partition([d for p, d in distances])
        first_layer = [p for p, d in distances[:layer_break]]
        second_layer = [p for p, d in distances[layer_break:]]

        # Optimise for the puck center by finding the point that is equidistant from every point in each layer
        center = fmin(func=_center_minimiser,
                      x0=centroid.tuple(),
                      args=tuple([[first_layer, second_layer]]),
                      xtol=1,
                      disp=False)
        center = Point(center[0], center[1]).intify()

        return center
Exemple #10
0
 def test_draw_circle_thickness_equals_one(self):
     img = Image.blank(40, 40)
     circle = Circle(Point(20, 20), 10)
     img.draw_circle(circle, Color(200, 200, 200), 1)
     self.assertEquals(img.img[10][20][1], 200)
     self.assertEquals(img.img[9][20][1], 0)
     self.assertEquals(img.img[11][20][1], 0)
Exemple #11
0
    def _sanitize_circles(raw_circles):
        circles = []
        if raw_circles is not None:
            for raw in raw_circles[0]:
                center = Point(int(raw[0]), int(raw[1]))
                radius = int(raw[2])
                circles.append(Circle(center, radius))

        return circles
Exemple #12
0
def _rotate_around_point(point, angle, center):
    """ Rotate the point about the center position """
    x = point.x - center.x
    y = point.y - center.y
    cos = math.cos(angle)
    sin = math.sin(angle)
    x_ = x * cos - y * sin
    y_ = x * sin + y * cos
    return Point(x_, y_) + center
Exemple #13
0
 def draw_text(self, text, position, color, centered=False, scale=1.5, thickness=3):
     """ Draw the specified text on the image (in place). """
     if centered:
         text_size = opencv.getTextSize(text, opencv.FONT_HERSHEY_SIMPLEX, fontScale=scale, thickness=thickness)[0]
         text_size = Point(-text_size[0]/2.0, text_size[1]/2.0)
         position = (position + text_size)
     position = self._format_point(position)
     opencv.putText(self.img, text, position.tuple(), opencv.FONT_HERSHEY_SIMPLEX, fontScale=scale,
                 color=color.bgra(), thickness=thickness)
    def deserialize(string):
        """ Generate a Unipuck object from a string representation. """
        tokens = string.split(Unipuck._SERIAL_DELIM)

        center = Point(int(tokens[0]), int(tokens[1]))
        radius = int(tokens[2])
        angle = float(tokens[3])

        return Unipuck(center, radius, angle)
def CIRCLES_DEMO():
    """ Draw a set of axes and a random set of circles. Perform a series of
    random transformations on the circles.
    """
    # Create a set of random circles
    points = []
    for i in range(10):
        X = (random.random()) * 200
        Y = (random.random()) * 200
        points.append(Point(X, Y))

    for i in range(10):
        # Create random transformation
        angle = random.random() * 2 * math.pi
        scale = random.random() * 3
        delX = (random.random() - 0.5) * 200
        delY = (random.random() - 0.5) * 200
        translate = Point(delX, delY)
        trs = Transform(translate, angle, scale)

        # Display on image
        image = Image.blank(1000, 800)
        image.draw_offset = IMG_CENTER
        draw_axes(image, 300)

        # Draw the circles and transformed circles on the image
        radius = 10
        for p in points:
            circle = Circle(p, radius)
            trans_circle = Circle(trs.transform(p), radius * trs.zoom)
            image.draw_circle(circle, Color.Red())
            image.draw_circle(trans_circle, Color.Blue())

        # Write the transformation on the image
        image.draw_text(trs.__str__(),
                        Point(-450, 350),
                        Color.White(),
                        centered=False,
                        scale=0.5,
                        thickness=1)

        # Show the image
        image.popup()
Exemple #16
0
 def test_sub_image_is_created_if_the_center_is_outside_but_the_radius_ovelaps_with_input_image(
         self):
     image = Image.blank(9, 9, 3, 0)
     x_center = 10
     y_center = 10
     radius = 2
     sub_image, roi = image.sub_image(Point(x_center, y_center), radius)
     height = sub_image.height
     width = sub_image.width
     self.assertEquals(width, 1)
     self.assertEquals(height, 1)
Exemple #17
0
 def test_sub_image_has_size_of_input_image_if_2xradius_covers_the_whole_image(
         self):
     image = Image.blank(5, 6, 3, 0)
     x_center = 2
     y_center = 2
     radius = 7
     sub_image, roi = image.sub_image(Point(x_center, y_center), radius)
     height = sub_image.height
     width = sub_image.width
     self.assertEquals(width, image.width)
     self.assertEquals(height, image.height)
Exemple #18
0
 def test_sub_image_has_size_0x0_if_center_and_radius_outside_the_input_image(
         self):
     image = Image.blank(5, 6, 3, 0)
     x_center = 10
     y_center = 10
     radius = 2
     sub_image, roi = image.sub_image(Point(x_center, y_center), radius)
     height = sub_image.height
     width = sub_image.width
     self.assertEquals(width, 0)
     self.assertEquals(height, 0)
Exemple #19
0
    def test_circle_is_correctly_detected_when_there_there_are_two_circles_in_the_image_not_intersecting(
            self):

        img = Image.blank(100, 100)
        circle_a = Circle(Point(20, 20), 10)
        circle_b = Circle(Point(50, 50), 10)
        img.draw_circle(circle_a, Color(10, 50, 100), 2)
        img.draw_circle(circle_b, Color(10, 50, 100), 2)
        grey = img.to_grayscale()

        decorator = CircleDetector()
        decorator.set_maximum_radius(20)
        decorator.set_minimum_radius(5)
        decorator.set_accumulator_threshold(30)
        decorator.set_canny_threshold(30)
        decorator.set_minimum_separation(10)

        list = decorator.find_circles(grey)

        self.assertEqual(list.__len__(), 2)
Exemple #20
0
 def test_sub_image_is_not_square_if_center_x_is_too_close_to_the_edge(
         self):
     image = Image.blank(10, 10, 3, 0)
     x_center = 9
     y_center = 5
     radius = 2
     sub_image, roi = image.sub_image(Point(x_center, y_center), radius)
     height = sub_image.height
     width = sub_image.width
     self.assertEquals(width, 3)
     self.assertEquals(height, 2 * radius)
     self.assertNotEquals(width, height)
def _center_minimiser(center, dist):
    """ Used as the cost function in an optimisation routine.
    for a trial center point, we calculate an error that is the sum of the squares of the deviation
    of the distance of each slot from the mean distance of all the slots.
    """
    errors = []
    center = Point.from_array(center)
    distances = [p.distance_to_sq(center) for p in dist]
    mean = np.mean(distances)
    layer_errors = [(d - mean)**2 for d in distances]
    errors.extend(layer_errors)

    return sum(errors)
Exemple #22
0
def _draw_square(image, transform, size):
    radius = size/2
    center = Point(transform.x, transform.y)
    rotated = image.rotate(transform.rot, center)

    x1, y1 = center.x-radius, center.y-radius
    x2, y2 = x1 + size, y1 + size

    roi = (x1, y1, x2, y2)
    marked_img = rotated.to_alpha()
    marked_img.draw_rectangle(roi, Color.Green(), 1)

    return marked_img
Exemple #23
0
    def _make_transforms(transform, grid_points, angle_points):
        transforms = []

        for x in grid_points:
            for y in grid_points:
                for degrees in angle_points:
                    radians = degrees * math.pi / 180
                    offset = Point(x, y)
                    trs = transform.by_offset(offset)
                    trs = trs.by_rotation(radians)
                    transforms.append(trs)

        return transforms
Exemple #24
0
 def test_draw_circle_thickness_equals_four(self):
     img = Image.blank(40, 40)
     circle = Circle(Point(20, 20), 10)
     img.draw_circle(circle, Color(200, 200, 200), 4)
     self.assertEquals(img.img[6][20][1], 0)
     self.assertEquals(img.img[7][20][1], 0)  #!!
     self.assertEquals(img.img[8][20][1], 200)
     self.assertEquals(img.img[10][20][1], 200)
     self.assertEquals(img.img[9][20][1], 200)
     self.assertEquals(img.img[11][20][1], 200)
     self.assertEquals(img.img[12][20][1], 200)
     self.assertEquals(img.img[13][20][1], 0)  #!!
     self.assertEquals(img.img[14][20][1], 0)
Exemple #25
0
    def test_calculate_slot_bounds_returns_x0_for_slots1and6_if_rotation_and_center_are_0(
            self):
        #again the assumption that slot bounds have a particular sequence - slot bound[0] is slot number 1
        center = Point(0, 0)
        rotation = 0
        radius = 10

        slot_bounds = Unipuck.calculate_slot_bounds(center, radius, rotation)
        self.assertTrue(len(slot_bounds) == 16)
        slot1 = slot_bounds[0]
        self.assertTrue(slot1.center().x == 0)
        slot6 = slot_bounds[5]
        self.assertTrue(slot6.center().x == 0)
Exemple #26
0
    def __init__(self, img):
        self.img = img

        size = self.img.shape
        self.width = size[1]
        self.height = size[0]

        if len(size) > 2:
            self.channels = size[2]
        else:
            self.channels = 1

        # All draw requests will be offset by this amount
        self.draw_offset = Point(0, 0)
Exemple #27
0
def _draw_finder_pattern(image, transform, fp):
    center = Point(transform.x, transform.y)
    angle = transform.rot
    rotated = image.rotate(transform.rot, center)

    c1 = _rotate_around_point(fp.c1, -angle, center)
    c2 = _rotate_around_point(fp.c2, -angle, center)
    c3 = _rotate_around_point(fp.c3, -angle, center)

    img = rotated.to_alpha()
    img.draw_line(c1, c2, Color.Green(), 1)
    img.draw_line(c1, c3, Color.Green(), 1)

    return img
    def _get_finder_pattern(edges):
        """Return information about the "main" corner from a set of edges.

        This function finds the corner between the longest two edges, which should
        be spatially adjacent (it is up to the caller to make sure of this). It
        returns the position of the corner, and vectors corresponding to the said
        two edges, pointing away from the corner. These two vectors are returned in
        an order such that their cross product is positive, i.e. (see diagram) the
        base vector (a) comes before the side vector (b).

              ^side
              |
              |   base
              X--->
         corner

        This provides a convenient way to refer to the position of a datamatrix.
        """
        self = ContourLocator

        i, j = self._longest_pair_indices(edges)
        pair_longest_edges = [edges[x] for x in (i, j)]
        x_corner = self._get_shared_vertex(*pair_longest_edges)
        c, d = map(partial(self._get_other_vertex, x_corner),
                   pair_longest_edges)
        vec_c, vec_d = map(partial(np.add, -x_corner), (c, d))

        # FIXME: There seems to be a sign error here...
        if vec_c[0] * vec_d[1] - vec_c[1] * vec_d[0] < 0:
            vec_base, vec_side = vec_c, vec_d
        else:
            vec_base, vec_side = vec_d, vec_c

        x_corner = Point(x_corner[0], x_corner[1]).intify()
        vec_base = Point(vec_base[0], vec_base[1]).intify()
        vec_side = Point(vec_side[0], vec_side[1]).intify()
        return FinderPattern(x_corner, vec_base, vec_side)
Exemple #29
0
    def test_draw_circle_centre_is_kept(self):
        img = Image.blank(40, 40)
        circle = Circle(Point(20, 20), 1)
        img.draw_circle(circle, Color(200, 200, 200), 1)
        self.assertEquals(img.img[18][20][1], 0)
        self.assertEquals(img.img[19][20][1], 200)
        self.assertEquals(img.img[20][20][1], 0)
        self.assertEquals(img.img[21][20][1], 200)
        self.assertEquals(img.img[22][20][1], 0)

        self.assertEquals(img.img[20][18][1], 0)
        self.assertEquals(img.img[20][19][1], 200)
        self.assertEquals(img.img[20][20][1], 0)
        self.assertEquals(img.img[20][21][1], 200)
        self.assertEquals(img.img[20][22][1], 0)
Exemple #30
0
    def test_sub_image_is_square_with_side_length_2_radius_if_enough_space_to_cut_from(
            self):
        image = Image.blank(10, 10, 3, 0)

        x_center = 5
        y_center = 5
        radius = 2
        sub_image, roi = image.sub_image(Point(
            x_center, y_center), radius)  #sub_image returns a raw cv image

        height = sub_image.height
        width = sub_image.width
        self.assertEquals(width, 2 * radius)
        self.assertEquals(height, 2 * radius)
        self.assertEquals(width, height)
def _center_minimiser(center, layers):
    """ Used as the cost function in an optimisation routine. The puck consists of 2 layers of slots.
    Within a given layer, each slot is the same distance from the center point of the puck. Therefore
    for a trial center point, we calculate an error that is the sum of the squares of the deviation
    of the distance of each slot from the mean distance of all the slots in the layer.
    """
    errors = []
    center = Point.from_array(center)
    for layer in layers:
        distances = [p.distance_to_sq(center) for p in layer]

        mean = np.mean(distances)
        layer_errors = [(d-mean)**2 for d in distances]
        errors.extend(layer_errors)

    return sum(errors)
Exemple #32
0
    def _locate_finder_in_square(image, transform, size):
        """ For the located barcode in the image, identify which of the sides make
        up the finder pattern.
        """
        radius = int(round(size/2))
        center = transform.trans
        angle = transform.rot

        rotated = image.rotate(angle, center)

        sx1, sy1 = center.x-radius, center.y-radius
        sx2, sy2 = center.x+radius, center.y+radius
        thick = int(round(size / 14))

        # Top
        x1, y1 = sx1, sy1
        x2, y2 = sx2, sy1 + thick
        top = np.sum(rotated.img[y1:y2, x1:x2]) / (size * thick)

        # Left
        x1, y1 = sx1, sy1
        x2, y2 = sx1 + thick, sy2
        left = np.sum(rotated.img[y1:y2, x1:x2]) / (size * thick)

        # Bottom
        x1, y1 = sx1, sy2 - thick
        x2, y2 = sx2, sy2
        bottom = np.sum(rotated.img[y1:y2, x1:x2]) / (size * thick)

        # Right
        x1, y1 = sx2 - thick, sy1
        x2, y2 = sx2, sy2
        right = np.sum(rotated.img[y1:y2, x1:x2]) / (size * thick)

        # Identify finder edges
        if top < bottom and left < right:
            c1 = [sx1, sy1]
            c2 = [sx1, sy2]
            c3 = [sx2, sy1]
        elif top < bottom and right < left:
            c1 = [sx2, sy1]
            c2 = [sx1, sy1]
            c3 = [sx2, sy2]
        elif bottom < top and left < right:
            c1 = [sx1, sy2]
            c2 = [sx2, sy2]
            c3 = [sx1, sy1]
        elif bottom < top and right < left:
            c1 = [sx2, sy2]
            c2 = [sx2, sy1]
            c3 = [sx1, sy2]
        else:
            return None

        # rotate points around center of square
        c1 = _rotate_around_point(Point.from_array(c1), angle, center)
        c2 = _rotate_around_point(Point.from_array(c2), angle, center)
        c3 = _rotate_around_point(Point.from_array(c3), angle, center)

        # Create finder pattern
        c1 = c1.intify()
        side1 = (c2 - c1).intify()
        side2 = (c3 - c1).intify()
        fp = FinderPattern(c1, side1, side2)

        return fp