def build_structure(file_name, time=False): """ Creates a multi layer list structure from the strokes of the given xml. :param file_name: Name of the XML. :param time: Flag, whether the time data from the xml is required. :return: The structured list of the XML. """ with open(file_name, 'r') as file: tree = ElementTree.parse(file) root = tree.getroot() strokes = [] # StrokeSet tag stores the strokes in the xml if time: for index in range(len(root.find('StrokeSet'))): strokes.append([(util.Point(float(point.attrib['x']), float(point.attrib['y'])), float(point.attrib['time'])) for point in root.find('StrokeSet')[index][:]]) else: for index in range(len(root.find('StrokeSet'))): strokes.append([(util.Point(float(point.attrib['x']), float(point.attrib['y']))) for point in root.find('StrokeSet')[index][:]]) return strokes
def clearUnderRobot(self, grid, robotPose): rr = (int(gridMap.robotRadius / grid.xStep) - 1) * grid.xStep corners = \ [grid.pointToIndices(robotPose.transformPoint(util.Point(rr, rr))), grid.pointToIndices(robotPose.transformPoint(util.Point(rr, -rr))), grid.pointToIndices(robotPose.transformPoint(util.Point(-rr, rr))), grid.pointToIndices(robotPose.transformPoint(util.Point(-rr, -rr)))] minX = min([cx for (cx, cy) in corners]) maxX = max([cx for (cx, cy) in corners]) minY = min([cy for (cx, cy) in corners]) maxY = max([cy for (cx, cy) in corners]) for ix in range(minX, maxX + 1): for iy in range(minY, maxY + 1): grid.clearCell((ix, iy))
def parse_files(location): """ Iterates through the Data directory and gathers the strokes from all of the xml files. :param location: String, contains the root directory of the xml files. """ loc = copy.copy(location) content = os.listdir(loc) # Iteration into the deepest directory layer through recursive calls. if len([file for file in content if isdir(join(loc, str(file)))]) == 0: for f in content: try: tree = ElementTree.parse(join(loc, str(f))) root = tree.getroot() strokes = [] # root[3] marks the StrokeSet tag of the xml, which contains the list of strokes. # Each stroke has a set of x,y coordinates that describe the curve. for stroke in root.find('StrokeSet'): strokes.append([(util.Point(float(point.attrib['x']), float(point.attrib['y']))) for point in stroke]) data.append(strokes) except IOError as e: print('I/O error({0}): {1}.'.format(e.errno, e.strerror)) else: for d in content: parse_files(join(loc, str(d)))
def sonarHit(distance, sonarPose, robotPose): """ @param distance: distance along ray that the sonar hit something @param sonarPose: C{util.Pose} of the sonar on the robot @param robotPose: C{util.Pose} of the robot in the global frame @return: C{util.Point} representing position of the sonar hit in the global frame. """ return robotPose.transformPoint(sonarPose.transformPoint(\ util.Point(distance,0)))
def sonarHit(distance, sonarPose, robotPose): """ :param distance: distance along ray that the sonar hit something :param sonarPose: ``util.Pose`` of the sonar on the robot :param robotPose: ``util.Pose`` of the robot in the global frame :return: ``util.Point`` representing position of the sonar hit in the global frame. """ return robotPose.transformPoint(sonarPose.transformPoint(\ util.Point(distance,0)))
class SoarWorld: """ Represents a world in the same way as the soar simulator """ def __init__(self, path): """ @param path: String representing location of world file """ self.walls = [] """ Walls represented as list of pairs of endpoints """ self.wallSegs = [] """ Walls represented as list of C{util.lineSeg} """ # set the global world global world world = self # execute the file for side effect on world execfile(path) # put in the boundary walls (dx, dy) = self.dimensions wall((0, 0), (0, dy)) wall((0, 0), (dx, 0)) wall((dx, 0), (dx, dy)) wall((0, dy), (dx, dy)) def initialLoc(self, x, y): # Initial robot location self.initialRobotLoc = util.Point(x, y) def dims(self, dx, dy): # x and y dimensions self.dimensions = (dx, dy) def addWall(self, (xlo, ylo), (xhi, yhi)): # walls are defined by two points self.walls.append((util.Point(xlo, ylo), util.Point(xhi, yhi))) # also store representation as line segments self.wallSegs.append( util.LineSeg(util.Point(xlo, ylo), util.Point(xhi, yhi)))
def main(input_file): astroids = [] y = 0 x = 0 with open(input_file) as f: while True: line = f.readline().strip() if not line: break for char in line: if char == "#": astroids.append(util.Point(x, y)) x += 1 y += 1 x = 0 laser_location = util.Point(26, 28) #laser_location = util.Point(11,13) # Other astroids astroids = list(filter(lambda x: x != laser_location, astroids)) logging.debug("Astroids: " + ",".join(str(x) for x in astroids)) destroyed_count = 0 target_count = 200 while destroyed_count < target_count: can_see = can_see_list(laser_location, astroids) can_see.sort(key=lambda x: -vector_angle(vector(laser_location, x))) #logging.debug("Can See: " + ",".join(str(x) for x in can_see)) for target in can_see: destroyed_count += 1 logging.debug(f"Blowing up {target} - #{destroyed_count}") astroids.remove(target) if destroyed_count == target_count: print("Answer: " + str((target.x * 100) + target.y)) break
def indicesToBoxSegs(self, indices): """ @param indices: pair of C{(ix, iy)} indices of a grid cell @returns: list of four line segments that constitute the boundary of the cell, grown by the radius of the robot, which is found in C{gridMap.robotRadius}. """ center = self.indicesToPoint(indices) xs = self.xStep/2 + gridMap.robotRadius ys = self.yStep/2 + gridMap.robotRadius vertices = [center + util.Point(xs, ys), center + util.Point(xs, -ys), center + util.Point(-xs, -ys), center + util.Point(-xs, ys)] segs = [util.LineSeg(vertices[0], vertices[1]), util.LineSeg(vertices[1], vertices[2]), util.LineSeg(vertices[2], vertices[3]), util.LineSeg(vertices[3], vertices[0])] return segs
def get_stroke_parameters(stroke, file_index, stroke_index): """ Calculates the parameters of a stroke and concatenates it with the predetermined horizontal value. :param stroke: A single stroke of the text. :param file_index: The index of the file, which contains the currently processed stroke. :param stroke_index: The index of the stroke in the StrokeSet. :return: The calculated parameters of the stroke. The values are Nan, if the stroke is too short. """ h_line_avg_distance = 0 d_line_avg_distance = 0 stroke_length = 0 avg_degree = 0 # In case of comas, dots, or xml errors the stroke is marked with null values, and will be removed from the # training data in the next processing step. if len(stroke) == 0 or len(stroke) == 1 or len(stroke) == 2: return None, None, None, 0, None else: for index in range(len(stroke)): try: if index in range(0, len(stroke) - 1): stroke_length += util.point_2_point( stroke[index], stroke[index + 1]) avg_degree += math.fabs( util.calculate_angle( stroke[0], stroke[index + 1])) / (len(stroke) - 1) if index in range(1, len(stroke) - 1): # Average distance of the stroke's points from the line, # that connects the first and the final point. d_line_avg_distance += util.point_2_line( stroke[0], stroke[-1], stroke[index]) / (len(stroke) - 2) if index in range(1, len(stroke)): # Average distance of the stroke's points from the horizontal line, # that goes through the first point. h_line_avg_distance += util.point_2_line( stroke[0], util.Point(stroke[0].x + 1, stroke[0].y), stroke[index]) / (len(stroke) - 1) except ZeroDivisionError: # In case of division error, that occurs during the calculation of the angle (due to faulty xml data) # ignore the point and move to the next. pass if stroke_index == 13: print(avg_degree, h_line_avg_distance, d_line_avg_distance, stroke_length, is_horizontal(file_index, stroke_index), file_index) return avg_degree, h_line_avg_distance, d_line_avg_distance, stroke_length, is_horizontal( file_index, stroke_index)
def createTrackers(self, im, frameIndex, frame, blobKeypoints): # bbox :: Bounding Box # Add new trackers for blobs whose keypoint location isn't inside a tracker bbox. _numberOfAdditions = 0 for keypoint in blobKeypoints: if any( util.Point(*keypoint.pt) in mvTracker.mvbbox for mvTracker in self.trackerList): # ptx, pty = map(int, blob.pt) # logger.debug(f"Dismissed:: Blob at {ptx, pty}.") continue # Do not create a tracker = continue to next iteration # Get and draw bbox: x, y = keypoint.pt blobMvbbox = getBlobMvBbox(self.logger, im["dilateC"], x, y) if blobMvbbox.center.y < self.vidDimension[ 1] * self.trackingConfig.trackingXminRatio: continue # width_on_height_ratio = blobMvbbox.width / blobMvbbox.height # width_on_height_ratio = 0.5 # Create and register tracker: if blobMvbbox.area > 0: try: mvTracker = MvTracker( logger=self.logger, frameIndex=frameIndex, frame=frame, Extractor=self.Extractor, trackingConfig=self.trackingConfig, bbox=blobMvbbox.bbox, tracker=self.mvTrackerCreator(), im=im, ) if mvTracker.isFinishedTracker(self.vidDimension): continue except ValueError: continue else: continue blue = (255, 0, 0) mvTracker.mvbbox.draw(im["trackers"], blue, thickness=6) self.trackerList.append(mvTracker) _numberOfAdditions += 1 return _numberOfAdditions
def initialLoc(self, x, y): # Initial robot location self.initialRobotLoc = util.Point(x, y)
def main(input_file): astroids = [] y = 0 x = 0 with open(input_file) as f: while True: line = f.readline().strip() if not line: break for char in line: if char == "#": astroids.append(util.Point(x,y)) x+=1 y+=1 x=0 logging.debug("Astroids: " + ",".join(str(x) for x in astroids)) cansee_map = { x:list() for x in astroids } for source in astroids: logging.debug(f"Checking what {source} can see...") possible_targets = [ x for x in astroids if x != source and x not in cansee_map[source] ] while len(possible_targets) > 0: target = possible_targets.pop() blockers = [ x for x in astroids if x not in [source, target] ] can_see_target = True for other in blockers: if not is_collinear(source, other, target): continue # Not collinear so not a possible blocker logging.debug(f"{source} {other} {target} are collinear") # Check if other is between source and target (blocks target) # https://lucidar.me/en/mathematics/check-if-a-point-belongs-on-a-line-segment/ k_source_other = (target.x - source.x) * (other.x - source.x) + (target.y - source.y) * (other.y - source .y) k_source_target = (target.x - source.x)**2 + (target.y - source.y)**2 if 0 < k_source_other < k_source_target: logging.debug(f"{other} blocks {source}->{target}") can_see_target = False break if can_see_target: cansee_map[source].append(target) cansee_map[target].append(source) max_astroid = None max_count = 0 for astroid in cansee_map: if len(cansee_map[astroid]) > max_count: max_astroid = astroid max_count = len(cansee_map[astroid]) print(f"{max_astroid} can see the most other astroids ({max_count})")
class GridMap: def __init__(self, xMin, xMax, yMin, yMax, gridSquareSize, windowWidth = defaultWindowWidth): """ Basic initializer that determines the number of cells, and calls the C{makeStartingGrid} method that any subclass must provide, to get the initial values. Makes a window and draws the initial world state in it. @param xMin: least real x coordinate @param xMax: greatest real x coordinate @param yMin: least real y coordinate @param yMax: greatest real y coordinate @param gridSquareSize: size, in world coordinates, of a grid square @param windowWidth: size, in pixels, to make the window for drawing this map """ self.xMin = xMin """X coordinate of left edge""" self.xMax = xMax """X coordinate of right edge""" self.yMin = yMin """Y coordinate of bottom edge""" self.yMax = yMax """Y coordinate of top edge""" self.xN = int(math.ceil((self.xMax - self.xMin) / gridSquareSize)) """number of cells in x dimension""" self.yN = int(math.ceil((self.yMax - self.yMin) / gridSquareSize)) """number of cells in y dimension""" self.xStep = gridSquareSize """size of a side of a cell in the x dimension""" self.yStep = gridSquareSize """size of a side of a cell in the y dimension""" ## Readjust the max dimensions to handle the fact that we need ## to have a discrete numer of grid cells self.xMax = gridSquareSize * self.xN + self.xMin self.yMax = gridSquareSize * self.yN + self.yMin self.grid = self.makeStartingGrid() """values stored in the grid cells""" self.graphicsGrid = util.make2DArray(self.xN, self.yN, None) """graphics objects""" self.makeWindow(windowWidth) self.drawWorld() def makeWindow(self, windowWidth = defaultWindowWidth, title = 'Grid Map'): """ Create a window of the right dimensions representing the grid map. Store in C{self.window}. """ dx = self.xMax - self.xMin dy = self.yMax - self.yMin maxWorldDim = float(max(dx, dy)) margin = 0.01*maxWorldDim margin = 0.0*maxWorldDim self.window = dw.DrawingWindow(int(windowWidth*dx/maxWorldDim), int(windowWidth*dy/maxWorldDim), self.xMin - margin, self.xMax + margin, self.yMin - margin, self.yMax + margin, title) windows.windowList.append(self.window) def xToIndex(self, x): """ @param x: real world x coordinate @return: x grid index it maps into """ shiftedX = x - self.xStep/2.0 return util.clip(int(round((shiftedX-self.xMin)/self.xStep)), 0, self.xN-1) def yToIndex(self, y): """ @param y: real world y coordinate @return: y grid index it maps into """ shiftedY = y - self.yStep/2.0 return util.clip(int(round((shiftedY-self.yMin)/self.yStep)), 0, self.yN-1) def indexToX(self, ix): """ @param ix: grid index in the x dimension @return: the real x coordinate of the center of that grid cell """ return self.xMin + float(ix)*self.xStep + self.xStep/2.0 def indexToY(self, iy): """ @param iy: grid index in the y dimension @return: the real y coordinate of the center of that grid cell """ return self.yMin + float(iy)*self.yStep + self.yStep/2.0 def pointToIndices(self, point): """ @param point: real world point coordinates (instance of C{Point}) @return: pair of (x, y) grid indices it maps into """ return (self.xToIndex(point.x),self.yToIndex(point.y)) def indicesToPoint(self, (ix,iy)): """ @param ix: x index of grid cell @param iy: y index of grid cell @return: c{Point} in real world coordinates of center of cell """ return util.Point(self.indexToX(ix), self.indexToY(iy))
def get_outlier_points(stroke, estimated_position, limit): """ Finds the group of points, that is the closest to the stroke's estimated location, and returns the list of those points, which are not in this group. :param stroke: The inspected stroke. :param estimated_position: The stroke's estimated position. :param limit: The length limit of an edge between two vertices in the graph. The graph consists of the points of the stroke and it is represented as a graph for the algorithm that groups the points. :return: Ordered list of the outlier points' indexes. """ def index_to_point(indexes, point_objects): """ Gets the corresponding point objects in the stroke for the given set of indexes. :param indexes: Indexes to be interpreted as points. :param point_objects: A single stroke. :return: List of point objects. """ return [ point for point_index, point in enumerate(point_objects) if point_index in indexes ] # The connected vertices are stored as ones in the matrix. adjacency_matrix = np.ones((len(stroke), len(stroke))) for row in range(len(adjacency_matrix)): for col in range(len(adjacency_matrix[row])): if row == col: adjacency_matrix[row][col] = -1 elif util.point_2_point(stroke[row], stroke[col]) > limit: adjacency_matrix[row][col] = 0 # The matrix is converted into a dict, that stores the vertex sequence numbers as keys, and # the corresponding connected vertices as values. adjacency_list = OrderedDict() for index, row in enumerate(adjacency_matrix): adjacency_list[index] = util.find_all(row, 1) # The connected vertices are organised into groups. groups = [] while len(adjacency_list) > 0: group = util.dfs(adjacency_list) if len(group) != 0: groups.append(group) for index in group: if index in adjacency_list: del adjacency_list[index] # The groups are represented by their average position. average_positions = [] for group in groups: average_positions.append( util.get_average_point(index_to_point(group, stroke))) # The distances between the average position and the predicted location is calculated. distances = [] for position in average_positions: distances.append( util.point_2_point( position, util.Point(estimated_position[1], estimated_position[2]))) # The group that is closest to the predicted location is chosen. closest_group = distances.index(min(distances)) return [ index for index, point in enumerate(stroke) if index not in groups[closest_group] ]
def predict_stroke_position(stroke_index, lines, strokes): """ Predicts the faulty stroke's position, based on the surrounding strokes. If the stroke is not on the edges of a line, it will be placed at the middle of the distance between the two adjacent strokes. If the stroke is the first or the final one, it will be placed at a location calculated by the parameters of the corresponding line. :param stroke_index: The index of the stroke in the stroke set. :param lines: The structured set of strokes, organised into lines. :param strokes: The set of strokes. :return: The sequence number of the line, in which the stroke has been determined to be in. The x and the y coordinates of the position. """ # The distances list stores the distance between the strokes' position in the line. distances = [] # The stroke is not at the first or final index. The values of the surrounding strokes can be used. if len(lines) > stroke_index > 0: # lines[stroke_index] is a tuple, of which first element is the sequence number of the line. # If the stroke's previous and next neighbours are in the same line, then the stroke is in that line. if lines[stroke_index - 1][0] == lines[stroke_index][0]: line_index = lines[stroke_index - 1][0] # If they are in different lines, then the stroke must be either at the end of the line or at the beginning. else: prev_median_y = util.get_average([ stroke[2] for stroke in lines if stroke[0] == lines[stroke_index - 1][0] ]) next_median_y = util.get_average([ stroke[2] for stroke in lines if stroke[0] == lines[stroke_index][0] ]) # The stroke will be placed in the line, in which the stroke is closest to its possible location. line_index = lines[stroke_index - 1][0] if\ util.point_2_set(util.Point(lines[stroke_index - 1][1], prev_median_y), strokes[stroke_index]) <\ util.point_2_set(util.Point(lines[stroke_index][1], next_median_y), strokes[stroke_index]) else lines[stroke_index][0] x_medians = [stroke[1] for stroke in lines if stroke[0] == line_index] for index, x_median in enumerate(x_medians[:-1]): distances.append( util.point_2_point(util.Point(x_median, 0), util.Point(x_medians[index + 1], 0))) # print(distances) # If the stroke was determined to be in the line of the previous stroke, then its x position is calculated by # adding the average of distances between the strokes' positions in that line, to the final stroke of the line. if line_index == lines[stroke_index - 1][0]: x_coordinate = lines[stroke_index - 1][1] + util.get_average(distances) # If its in the next line, the same principle is applied. else: x_coordinate = lines[stroke_index][1] - util.get_average(distances) # The stroke is the first in the stroke set. elif stroke_index == 0: line_index = lines[stroke_index][0] x_medians = [stroke[1] for stroke in lines if stroke[0] == line_index] for index, x_median in enumerate(x_medians[:-1]): distances.append( util.point_2_point(util.Point(x_median, 0), util.Point(x_medians[index + 1], 0))) x_coordinate = lines[stroke_index][1] - util.get_average(distances) # The stroke is the final stroke in the set. else: line_index = lines[stroke_index - 1][0] x_medians = [stroke[1] for stroke in lines if stroke[0] == line_index] for index, x_median in enumerate(x_medians[:-1]): distances.append( util.point_2_point(util.Point(x_median, 0), util.Point(x_medians[index + 1], 0))) x_coordinate = lines[stroke_index - 1][1] + util.get_average(distances) y_coordinate = util.get_average( [stroke[2] for stroke in lines if stroke[0] == line_index]) return line_index, x_coordinate, y_coordinate
def get_lines(data, faulty_strokes, file_name): """ Divides the stroke set into lines. :param data: Set of strokes. :param faulty_strokes: A list of strokes that have been determined as faulty, based on their stroke length to number of registered points ratio. :param file_name: The file that will be scanned for outliers. :return: The data structure containing the strokes separated according to the lines of the text. """ def get_nb_eol(file): """ Counts the EOLs in the text of the xml. :param file: The file that will be scanned for outliers. :return: Number of EOLs. """ tree = ElementTree.parse(file) root = tree.getroot() return root.find('Transcription').find('Text').text.strip().count('\n') # The calculation of the distances between a text's strokes. The strokes are represented as a single number, # the median value of the registered points' x coordinates. This step is directed to find the end of lines # in the written text, hence the values of the y coordinates are not necessary, since the outlying distances # can be found as the large jumps of distance values at the end the of lines. distances = [] # The length statistics are created only on the correct strokes, so the anomalies in the faulty strokes # will not interfere with the detection of EOL. correct_strokes = [ stroke for stroke_index, stroke in enumerate(data) if stroke_index not in faulty_strokes ] for stroke_index, stroke in enumerate(correct_strokes): median_x = util.get_quartiles([point.x for point in stroke])[2] if stroke_index < len(correct_strokes) - 1: next_median_x = util.get_quartiles( [point.x for point in correct_strokes[stroke_index + 1]])[2] distances.append( util.point_2_point(util.Point(median_x, 0), util.Point(next_median_x, 0))) distances.sort() # The largest distances will be the EOLs, so the last get_nb_eol(file_name)th element will be the distance limit. length_limit = distances[-get_nb_eol(file_name)] - 0.1 lines = [] index = 0 # Creation of the data structure, that stores the line sequence number, the x and the y median values of a stroke. for stroke_index, stroke in enumerate(correct_strokes): median_x = util.get_quartiles([point.x for point in stroke])[2] median_y = util.get_quartiles([point.y for point in stroke])[2] lines.append((index, median_x, median_y)) if stroke_index < len(correct_strokes) - 1: next_median_x = util.get_quartiles( [point.x for point in correct_strokes[stroke_index + 1]])[2] if util.point_2_point(util.Point(median_x, 0), util.Point(next_median_x, 0)) > length_limit: index += 1 # The list of faulty strokes are ignored in this step, since the iterated data is the list of correct strokes. # Reason for this is the extraordinary values in the faulty strokes, which prevent the correct calculation of # the stroke's location. faulty_strokes.sort() # The faulty strokes are inserted into the list in this step, with the predicted locations. for stroke_index in faulty_strokes: lines.insert(stroke_index, predict_stroke_position(stroke_index, lines, data)) return lines