Exemple #1
0
def derotate(marks, center, tangent):
    """Return new landmark pts calculated by rotating tangent about center. """
    pt_array = []
    #print marks
    for pt in (marks.ulc, marks.urc, marks.lrc, marks.llc):
        pt2 = Point(pt.x, pt.y)

        #rotate relative to center
        pt2.x -= center.x
        pt2.y -= center.y

        ra_sin = math.sin(-tangent * math.pi / 180.)
        ra_cos = math.cos(-tangent * math.pi / 180.)
        #print "SIN %2.1f COS %2.1f" % (ra_sin,ra_cos)
        #print "pt3.x= %2.1f minus %2.1f" % (pt2.x*ra_cos, pt2.y*ra_sin)
        #print "pt3.y=%2.1f plus %2.1f" % (pt2.x*ra_sin,pt2.y*ra_cos)
        pt3 = Point(pt2.x * ra_cos - pt2.y * ra_sin,
                    pt2.x * ra_sin + pt2.y * (ra_cos))

        # restore original center offset
        pt3.x += center.x
        pt3.y += center.y

        # restore y increasing downwards
        #pt2.y = -pt2.y

        pt3.x = int(pt3.x)
        pt3.y = int(pt3.y)
        pt_array.append(pt3)
    return Landmarks(pt_array[0], pt_array[1], pt_array[2], pt_array[3])
Exemple #2
0
def test_derotate():
    lm = Landmarks(Point(0, 0), Point(1000, 0), Point(1000, 1000),
                   Point(0, 1000))
    center = Point(500, 500)
    retlm = derotate(lm, center, 45)  # 45 degrees CCW

    print retlm
    return retlm
Exemple #3
0
    def find_landmarks(self, uli, uri, lri, lli):
        """ retrieve landmarks for Hart images

        Landmarks for the Hart Ballot will be the ulc, urc, lrc, llc 
        (x,y) pairs marking the four corners of the main surrounding box."""

        TOP = True
        BOT = False
        LEFT = True
        RIGHT = False
        lm = []

        hline = scan_strips_for_horiz_line_y(
            uli,
            const.dpi,
            uli.size[0] - const.dpi / 2,  #starting_x 
            const.dpi / 4,  #starting_y
            const.dpi / 2,  #height_to_scan
            TOP)
        x, y = follow_hline_to_corner(
            uli,
            const.dpi,
            uli.size[0],  #starting_x
            hline,  #starting_y
            LEFT)
        lm.append(Point(x, y))

        hline = scan_strips_for_horiz_line_y(
            uri,
            const.dpi,
            const.dpi / 2,  #starting_x
            const.dpi / 4,  #starting_y 
            const.dpi / 2,  #height to search
            TOP)
        x, y = follow_hline_to_corner(
            uri,
            const.dpi,
            const.dpi / 2,  #startx
            hline,  #hline
            RIGHT)
        lm.append(Point(x, y))

        hline = scan_strips_for_horiz_line_y(lri, const.dpi, const.dpi / 2,
                                             const.dpi / 4, const.dpi / 2, BOT)
        x, y = follow_hline_to_corner(lri, const.dpi, const.dpi / 2, hline,
                                      RIGHT)
        lm.append(Point(x, y))
        hline = scan_strips_for_horiz_line_y(lli, const.dpi,
                                             uli.size[0] - const.dpi / 2,
                                             const.dpi / 4, const.dpi / 2, BOT)
        x, y = follow_hline_to_corner(lli, const.dpi,
                                      uli.size[0] - const.dpi / 2, hline, LEFT)
        lm.append(Point(x, y))

        landmarks = Landmarks(lm[0], lm[1], lm[2], lm[3])
        return landmarks
Exemple #4
0
    def __init__(self,
                 dpi,
                 filename,
                 landmarks=None,
                 layout_id=None,
                 precinct="?",
                 vendor=None,
                 flip=False):
        self.dpi = dpi
        self.filename = filename
        self.landmarks = landmarks
        self.layout_id = layout_id
        self.precinct = precinct
        self.use_tint_test = False
        self.use_wide_bounded_test = False
        self.median_tangent = None
        self.image = Image.open(filename).convert("RGB")
        if flip:
            self.image = self.image.rotate(180.)
        self.draw_image = Image.new(self.image.mode, self.image.size,
                                    (255, 255, 255))
        self.draw = ImageDraw.Draw(self.draw_image)
        self.darkness_threshold = 208
        self.vendor_initialization(vendor)

        self.median_tangent = get_tangent(self.image, dpi=self.dpi)

        self.image = self.image.rotate(-self.median_tangent * 360. / 6.283)
        self.image.save("/tmp/rotated.jpg")
        # need to adjust landmarks at this point
        # by applying rotation about center to them
        center = Point(self.image.size[0] / 2, self.image.size[1] / 2)
        if self.landmarks:
            self.landmarks = derotate(self.landmarks, center,
                                      -self.median_tangent * 360 / 6.28)
        contestboxlist = find_contest_boxes(
            self.image,
            self.dpi / 30,  #skip_between_lines
            self.dpi / 6,  #min_extent
            dpi,  #min_line_break_length
            50,  #required_intensity_drop
            True,  #search_forwards
            dpi=dpi)

        # Merge grey zone artifact boxes into larger boxes
        # and append merged to our main boxlist.

        contestboxlist = merge_artifacts(contestboxlist, self.dpi)

        # returned list consists of bbox image pairs

        contestlist = self.cleanup_trim_and_draw(contestboxlist)

        self.ballot_text_array = []

        # eliminate duplicates via dictionary
        contestdict = {}
        for bbox, contest_image in contestlist:
            contestdict[tuple(bbox)] = contest_image

        # sort contest list by x, then by y
        contestlist = sorted(
            contestdict.keys(),
            key=lambda bbox: int(bbox[0]) * 100 + int(bbox[1]))

        for bbox in contestlist:
            #print bbox,contest_image
            box_text_array = self.process_box(bbox, contestdict[bbox])
            self.ballot_text_array.extend(box_text_array)
Exemple #5
0

def test_derotate():
    lm = Landmarks(Point(0, 0), Point(1000, 0), Point(1000, 1000),
                   Point(0, 1000))
    center = Point(500, 500)
    retlm = derotate(lm, center, 45)  # 45 degrees CCW

    print retlm
    return retlm


if __name__ == "__main__":
    if len(sys.argv) < 3:
        print "usage find_intensity_changes.py dpi file layout_id"
    dpi = int(sys.argv[1])
    #test_derotate()
    bt = BallotTemplate(dpi,
                        sys.argv[2],
                        landmarks=Landmarks(Point(212, 201), Point(2401, 237),
                                            Point(2335, 3996),
                                            Point(143, 3957)),
                        layout_id='id1',
                        precinct='P1',
                        vendor='Hart')

    bt_out = open("/tmp/bt_out.xml", "w")
    bt_out.write(bt.__repr__())
    bt_out.close()
    bt.draw_image.save("/tmp/viz%s.jpg" % (os.path.basename(sys.argv[2])))
Exemple #6
0
 def return_transformed(self,region):
     x,y = self.transform_coord(region.x,region.y)
     return Point(x,y)
Exemple #7
0
    def process_recursive(self, node, x, y):
        """Recursive walk through XML rooted at node.

        The process_recursive function walks an XML tree 
        generating VOPAnalyze instances for each Vote node of the tree.
        """

        if node.nodeType != Node.ELEMENT_NODE:
            return
        print source_line(), "node name: ", node.nodeName
        print source_line(), dumpdict(node.attributes)
        #pdb.set_trace()
        if node.nodeName == 'BallotSide':
            units = node.getAttribute('units')
            if units == '':
                self.units = 'pixels'
            elif units == 'pixels' or units == 'inches':
                self.units = units
            else:
                raise WalkerException("Ballot side specified unknown unit %s" %
                                      (units, ))
            self.side = node.getAttribute('side')
            self.layout_id = node.getAttribute('layout-id')

            # If the layout includes attributes
            # related to the target size, use them.
            # For missing attributes, use values from the config file.

            # TARGET HEIGHT
            th = node.getAttribute('target-height')
            if th == '':
                self.target_height = target_height
            else:
                self.target_height = float(th)

            # TARGET WIDTH
            tw = node.getAttribute('target-width')
            if tw == '':
                self.target_width = target_width
            else:
                self.target_width = float(tw)

            # PRECINCT
            precinct = node.getAttribute('precinct')
            if precinct == '':
                self.precinct = "NOTINTEMPLATE"
            else:
                self.precinct = precinct

            # PARTY
            party = node.getAttribute('party')
            if party == '':
                self.party = "NOTINTEMPLATE"
            else:
                self.party = party

            # TARGET HOTSPOT OFFSET X
            # (a target may begin visually before the area to be analyzed,
            # for example, it may consist of two printed arrow halves,
            # with the area to analyze centered between the two printed halves.)
            thox = node.getAttribute('target-hotspot-offset-x')
            if thox == '':
                self.target_hotspot_offset_x = target_hotspot_offset_x
            else:
                self.target_hotspot_offset_x = float(thox)

            # TARGET HOTSPOT OFFSET Y
            thoy = node.getAttribute('target-hotspot-offset-y')
            if thoy == '':
                self.target_hotspot_offset_y = target_hotspot_offset_y
            else:
                self.target_hotspot_offset_y = float(thoy)
            print source_line(), "target height: %s, width: %s" % (
                self.target_height, self.target_width)

        elif node.nodeName == 'Landmarks':
            # Set landmarks from node, building transformer
            try:
                ulc_x = float(node.getAttribute('ulc-x'))
                ulc_y = float(node.getAttribute('ulc-y'))
                urc_x = float(node.getAttribute('urc-x'))
                urc_y = float(node.getAttribute('urc-y'))
                llc_x = float(node.getAttribute('llc-x'))
                llc_y = float(node.getAttribute('llc-y'))
                lrc_x = float(node.getAttribute('lrc-x'))
                lrc_y = float(node.getAttribute('lrc-y'))
            except ValueError:
                raise WalkerException(
                    "Missing required attrib in Landmarks node of XML.")

            print source_line(), "Layout", ulc_x, ulc_y, urc_x, urc_y
            print source_line(
            ), "Image", self.landmarks.ulc, self.landmarks.urc
            self.transformer = Transformer(
                Point(ulc_x, ulc_y),  #layout
                self.landmarks.ulc,  #ballot
                Point(urc_x, urc_y),  #layout
                self.landmarks.urc,  #ballot
                Point(llc_x, llc_y),  #layout
                self.landmarks.llc  #ballot
            )
            print source_line(), "Transformer", self.transformer
        elif node.nodeName == 'Box':
            #Deal with a box by:
            #(1) changing our accumulated starting x and y positions;
            #(2) outputting appropriate data, if any, for the box
            #(3) setting juris, contest, etc... depending on the box's
            #    text attribute"""
            print source_line(), "old x,y =(%d, %d)" % (x, y)
            try:
                x = (x + float(node.getAttribute('x1')))
                y = (y + float(node.getAttribute('y1')))
            except ValueError:
                raise WalkerException(
                    "Missing required attrib in Box node of XML.")

            text = node.getAttribute('text')
            print source_line(), "new x,y =(%d, %d)" % (x, y)
            if text.upper().startswith('CONTEST:'):
                self.contest = text[8:]
            elif text.upper().startswith('JURISDICTION:'):
                self.jurisdiction = text[13:]
            else:
                self.contest = text
            try:
                self.max_votes = node.getAttribute('max-votes')
            except:
                self.max_votes = 1
                pass
            try:
                #print self.max_votes
                self.max_votes = int(self.max_votes)
                if (self.max_votes == 0):
                    self.max_votes = 1
                    print "Max votes was zero, set to 1."
                #print self.max_votes
            except:
                raise WalkerException(
                    "Failed to convert or receive max-votes attribute as integer."
                )
                self.max_votes = 1
            self.current_votes = 0
            # save the box node, so we can go to all its votes
            # if we turn out to have an overvote
            self.current_box_node = node
        elif node.nodeName == 'Vote':
            # Deal with a vote by adding its coordinates to the existing
            # surrounding box coordinates and transforming the result
            # to get actual coordinates to pass to an analysis object.
            # That object should probably hold the accumulating results,
            # not the BSW.
            attrib_x = None
            attrib_y = None
            attrib_name = None
            #max_votes = 1;
            try:
                attrib_x = float(node.getAttribute('x1'))
                attrib_y = float(node.getAttribute('y1'))
                attrib_name = node.getAttribute('text')
            except ValueError:
                raise WalkerException(
                    "Missing required attrib in Vote node of XML.")

            v_x, v_y = self.transformer.transform_coord((attrib_x + x),
                                                        (attrib_y + y))
            v_x2, v_y2 = self.transformer.transform_coord(
                (attrib_x + x + self.target_width),
                (attrib_y + y + self.target_height))
            #v_x = int(round(.998*v_x))
            v_y = int(round(v_y))
            v_y2 = int(round(v_y2))
            print source_line(), "(v_x=%d, vy=%d), (v_x2=%d, v_y2=%d)" % \
                                 (v_x, v_y, v_x2, v_y2)

            if abs(v_y2 - v_y) > 100:
                self.logger.error("Unreasonable values: %s %s %s %s" %
                                  (v_x, v_y, v_x2, v_y2))
                raise WalkerException("Unreasonable transformed values.")
            # Note that the material below is bypassed with an "if False"!!!
            # A class-specific fine adjustment routine to adjust the crop
            # the crop coordinates for vote areas may be passed in
            # as an initialization argument to the walker.  Otherwise,
            # it uses a version suited for Hart target boxes.
            # This can be generalized to a pre-vote-statistics call,
            # a post-vote-statistics call, and similar optional calls
            # for pre and post contest boxes and pre and post the entire image.
            if False:
                try:
                    #before_filename = "/tmp/before/BEFORE_%d_%d.jpg" % (
                    #    int(round(v_x)),int(round(v_y)))
                    #after_filename = "/tmp/after/AFTER_%d_%d.jpg" % (
                    #    int(round(v_x)),int(round(v_y)))
                    #self.image.crop((int(round(v_x)),
                    #                 int(round(v_y)),
                    #                 int(round(v_x2)),
                    #                 int(round(v_y2))
                    #                 )).save(before_filename)
                    self.logger.info(
                        "Recentering calculated crop %d %d %d %d." % (
                            int(round(v_x)),
                            int(round(v_y)),
                            int(round(v_x2)),
                            int(round(v_y2)),
                        ))
                    v_x, v_y, v_x2, v_y2 = self.ballot_class_vop_fine_adjust(
                        self.logger,
                        self.image,
                        int(round(v_x)),
                        int(round(v_y)),
                        int(round(v_x2)),
                        int(round(v_y2)),
                        #x,y margin in pixels, stored in BSW
                        self.mwp,
                        self.mhp,
                        #horiz and vert line thickness,stored in BSW,
                        # currently hardcoded to 1/32" as pixels
                        self.hlt,
                        self.vlt,
                        # target width and height in pixels,stored in BSW
                        self.twp,
                        self.thp)
                    self.logger.info("Recentered crop %d %d %d %d." % (
                        int(round(v_x)),
                        int(round(v_y)),
                        int(round(v_x2)),
                        int(round(v_y2)),
                    ))
                    #self.image.crop((int(round(v_x)),
                    #                 int(round(v_y)),
                    #                 int(round(v_x2)),
                    #                 int(round(v_y2))
                    #                 )).save(after_filename)
                except RecenteringException as e:
                    self.logger.warning(
                        "Recentering failed, target crop unchanged.")
                    self.logger.warning(e)
                except Exception as e:
                    self.logger.warning(
                        "Adjustment routine failed, ballot_class_vop_fine_adjust"
                    )
                    #pdb.set_trace()
                    self.logger.warning(e)

            # Provide margins to add to the bounding box
            # as arguments to VOPAnalyze;
            # the coordinates should continue to reflect
            # the exact boundaries of the vote op.
            mwp = int(round(const.margin_width_inches * const.dpi))
            mhp = int(round(const.margin_height_inches * const.dpi))

            vop = VOPAnalyze(int(round(v_x)),
                             int(round(v_y)),
                             int(round(v_x2)),
                             int(round(v_y2)),
                             v_margin=mhp,
                             h_margin=mwp,
                             image=self.image,
                             image_filename=self.image_filename,
                             side=self.side,
                             layout_id=self.layout_id,
                             jurisdiction=self.jurisdiction,
                             contest=self.contest,
                             choice=attrib_name,
                             max_votes=self.max_votes,
                             logger=self.logger)
            print source_line(), vop
            # if the vop was_voted, increment self.current_votes
            # if self.current_votes exceeds self.max_votes
            # for the moment, set the ambig flag on this vote op
            # it would be nice if we could walk back up to the box level
            # and flag every vote in the box as ambiguous or overvoted.

            if vop.voted:
                self.current_votes = self.current_votes + 1
                if self.current_votes > self.max_votes:
                    vop.ambiguous = True
                    # do something to flag every vote in self.current_box_node;
                    # meaning we have to buffer the vops
                    # until each box is completed
            # We need to buffer all vops for a box,
            # then flush them to the main
            # results only when we return from recursion.
            #print "Appending to box_results"
            #print vop
            self.box_results.append(vop)
            #print "Box results now"
            #print self.box_results
            #self.results.append(vop)
        else:
            self.logger.info("Unhandled element %s" % (node.nodeName, ))

        for n in node.childNodes:
            self.process_recursive(n, x, y)
            # set vote nodes overvoted if we have too many votes in box,
            # clear the was_voted flag, set ambiguous flag,
            # then flush pending box results to the main results,
        if self.box_results and node.nodeName == 'Box':
            #print "Self current votes %d > self.max_votes %d" % (self.current_votes,self.max_votes)
            #pdb.set_trace()
            # Don't call it an overvote if you encounter a situation
            # where an overvote would be caused by a slight mark in comparison
            # with one or more heavier marks
            lowest_intensity = 255
            highest_intensity = 0
            intensity_range = 0
            if self.current_votes > self.max_votes:
                for vop in self.box_results:
                    # determine the darkest and lightest vop
                    if vop.red_mean < lowest_intensity:
                        lowest_intensity = vop.red_mean
                    if vop.red_mean > highest_intensity:
                        highest_intensity = vop.red_mean
                intensity_range = highest_intensity - lowest_intensity
# commenting out next two for loops as failing, mjt 11/15/2015
#for vop in self.box_results:
#    if vop.red_mean > (lowest_intensity + (.9*intensity_range)):
#        vop.voted = False
#        vop.ambiguous = False
#        vop.overvoted = False
#        self.current_votes = self.current_votes - 1
# now, are any of the valid votes still ambiguous
#for vop in self.box_results:
#    if vop.voted and (vop.red_mean < (lowest_intensity + (.5*intensity_range))):
#        vop.ambiguous = False
#        vop.overvoted = False

            for vop in self.box_results:
                if self.current_votes > self.max_votes:
                    if vop.voted:
                        vop.overvoted = True
                        vop.ambiguous = True
                    vop.voted = False
                #print "Copying from box_results to results"
                #print vop
                self.results.append(vop)
            #print "Clearing box_results"
            #print self.box_results
            self.box_results = []
        return node
Exemple #8
0
def find_front_landmarks(im):
    """find the left and right corners of the uppermost line"""
    iround = lambda a: int(round(float(a)))
    adj = lambda a: int(round(const.dpi * a))
    width = adj(0.75)
    height = adj(0.75)
    # for testing, fall back to image argument if can't get from page
    # generate ulc, urc, lrc, llc coordinate pairs
    landmarks = []

    # use corners of top and bottom lines in preference to circled-plus
    # as the circled plus are often missed due to clogging, etc...
    try:
        a, b, c, d = find_line(im,
                               im.size[0] / 2,
                               100,
                               threshold=64,
                               black_sufficient=True)
        #self.log.debug("Top line coords (%d,%d)(%d,%d)" % (a,b,c,d))
    except Exception:
        pass
    else:
        landmarks.append(Point(a, b))
        landmarks.append(Point(c, d))

    try:
        # changing search start from 1/3" above bottom to 1/14" above
        a, b, c, d = find_line(im,
                               im.size[0] / 2,
                               im.size[1] - adj(0.07),
                               -adj(0.75),
                               threshold=64,
                               black_sufficient=True)
        #self.log.debug("Top line coords (%d,%d)(%d,%d)" % (a,b,c,d))
    except Exception:
        pass
    else:
        landmarks.append(Point(c, d))
        landmarks.append(Point(a, b))

    try:
        x, y = landmarks[0].x, landmarks[0].y
        longdiff_a = landmarks[3].y - landmarks[0].y
        shortdiff_a = landmarks[3].x - landmarks[0].x
        hypot = math.sqrt(longdiff_a * longdiff_a + shortdiff_a * shortdiff_a)
        r_a = float(shortdiff_a) / float(longdiff_a)
        longdiff_b = landmarks[1].x - landmarks[0].x
        shortdiff_b = landmarks[0].y - landmarks[1].y
        hypot = math.sqrt(longdiff_b * longdiff_b + shortdiff_b * shortdiff_b)
        r_b = float(shortdiff_b) / float(longdiff_b)
        magnitude_r = min(abs(r_a), abs(r_b))
        if r_a < 0. and r_b < 0.:
            sign_r = -1
        else:
            sign_r = 1
        r = magnitude_r * sign_r
    except IndexError:
        # page without landmarks; if this is a back page, it's ok
        raise Ballot.BallotException

    if abs(r) > 0.1:
        #self.log.info("Tangent is unreasonably high, at %f." % (r,))
        print "Tangent is unreasonably high, at %f." % (r, )
        #pdb.set_trace()
    # we depend on back landmarks being processed after front
    landmarks = Landmarks(landmarks[0], landmarks[1], landmarks[2],
                          landmarks[3])
    return landmarks