Example #1
0
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
Example #2
0
 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))
Example #3
0
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)))
Example #4
0
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)))
Example #5
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)))
Example #6
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)))
Example #7
0
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
Example #8
0
 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
             
             
Example #9
0
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)
Example #10
0
    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
Example #11
0
 def initialLoc(self, x, y):
     # Initial robot location
     self.initialRobotLoc = util.Point(x, y)
Example #12
0
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})")
Example #13
0
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))
Example #14
0
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]
    ]
Example #15
0
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
Example #16
0
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