def __init__(self, polygon: geometry_utils.Polygon): """Create a new LMark. If the points don't form a valid LMark, raises a WrongShapeError.""" if len(polygon) != 6: raise WrongShapeError("Incorrect number of points.") if not geometry_utils.all_approx_square(polygon): raise WrongShapeError("Corners are not square.") clockwise_polygon = geometry_utils.polygon_to_clockwise(polygon) side_lengths = geometry_utils.calc_side_lengths(clockwise_polygon) longest_sides_indexes = list_utils.find_greatest_value_indexes( side_lengths, n=2) if not list_utils.is_adjacent_indexes(side_lengths, *longest_sides_indexes): raise WrongShapeError("Longest sides are not adjacent.") # The longest sides should be about twice the length of the other sides unit_lengths = math_utils.divide_some(side_lengths, longest_sides_indexes, 2) if not math_utils.all_approx_equal(unit_lengths): raise WrongShapeError( "Longest sides are not twice the length of the other sides.") self.polygon = list_utils.arrange_index_to_first( clockwise_polygon, list_utils.determine_which_is_next(polygon, *longest_sides_indexes)) self.unit_length = math_utils.mean(unit_lengths)
def get_corner(square: Polygon, corner: Corner) -> Point: """Gets the point representing the given corner of the square. Square should be pretty close to vertical - horizontal. """ xs = [p.x for p in square] highest_xs = sorted(list_utils.find_greatest_value_indexes(xs, 2)) side_points = [ p for i, p in enumerate(square) if (corner.value[1] == 1 and i in highest_xs) or ( corner.value[1] == 0 and i not in highest_xs) ] side_ys = [p.y for p in side_points] [highest_y] = list_utils.find_greatest_value_indexes(side_ys, 1) corner_point = side_points[highest_y] if ( corner.value[0] == 0) else side_points[list_utils.next_index( side_points, highest_y)] return corner_point
def calculate_bubble_fill_threshold( field_fill_percents: tp.Dict[grid_info.Field, tp.List[tp.List[float]]], answer_fill_percents: tp.List[tp.List[tp.List[float]]], form_variant: grid_info.FormVariant, save_path: tp.Optional[pathlib.PurePath] = None) -> float: """Dynamically calculate the threshold to use for determining if a bubble is filled or unfilled. This is a time consuming function so it should only be called once per page. It works by getting the fill percentages of all the values in all the fields, sorting them, and finding the largest increase in fill percent between all the values in the highest 1/4 (assumes all the filled bubbles are less than 1/4 of all the bubbles). It then returns the average of the two values that were subtracted to make the largest increase. If `save_path` is provided, saves debugging data to this location as "threshold_values.txt". """ fill_percents_lists = list( field_fill_percents.values()) + answer_fill_percents fill_percents = [np.array(l).flatten() for l in fill_percents_lists] sorted_and_flattened = np.sort(np.concatenate(fill_percents)) last_chunk = sorted_and_flattened[-round(sorted_and_flattened.size / 5):] differences = [ last_chunk[i + 1] - last_chunk[i] for i in range(last_chunk.size - 1) ] biggest_diff_index = list_utils.find_greatest_value_indexes( differences, 1)[0] result = (last_chunk[biggest_diff_index] + last_chunk[biggest_diff_index + 1]) / 2 if save_path: with open(str(save_path / "threshold_values.txt"), "w+") as file: file.writelines([str(sorted_and_flattened), "\n\n", str(result)]) return result
def calculate_bubble_fill_threshold( grid: Grid, save_path: typing.Optional[pathlib.PurePath] = None) -> float: """Dynamically calculate the threshold to use for determining if a bubble is filled or unfilled. This is a time consuming function so it should only be called once per page. It works by getting the fill percentages of all the values in all the name fields, sorting them, and finding the largest increase in fill percent between all the values in the highest 1/4 (assumes all the filled bubbles are less than 1/4 of all the bubbles). It then returns the average of the two values that were subtracted to make the largest increase. If `save_path` is provided, saves debugging data to this location as "threshold_values.txt". """ # This function makes the following assumptions: # A. At least one bubble in first, last, or middle name is full. # B. Less than a tenth of the bubbles in first, last, and middle name are # full. For this to be untrue, an average of 2.6 bubbles per field would # be filled which doesn't make sense for anyone to do. # If these are not true, the values for the entire sheet will be useless. # However, these are most likely safe assumptions. fill_percents = [ np.array( get_group_from_info(grid_info.fields_info[field], grid).get_all_fill_percents()).flatten() for field in grid_info.Field ] sorted_and_flattened = np.sort(np.concatenate(fill_percents)) last_chunk = sorted_and_flattened[-round(sorted_and_flattened.size / 10):] differences = [ last_chunk[i + 1] - last_chunk[i] for i in range(last_chunk.size - 1) ] biggest_diff_index = list_utils.find_greatest_value_indexes( differences, 1)[0] result = (last_chunk[biggest_diff_index] + last_chunk[biggest_diff_index + 1]) / 2 if save_path: with open(str(save_path / "threshold_values.txt"), "w+") as file: file.writelines([str(sorted_and_flattened), "\n\n", str(result)]) return result