示例#1
0
    def centerline(self):
        """ determine the center line of the tail """
        mask, offset = self.mask
        dist_map = cv2.distanceTransform(mask, cv2.DIST_L2, 5)

        # setup active contour algorithm
        ac = ActiveContour(
            blur_radius=self.centerline_blur_radius,
            closed_loop=False,
            alpha=0,  #< line length is constraint by beta
            beta=self.centerline_bending_stiffness,
            gamma=self.centerline_adaptation_rate)
        ac.max_iterations = self.centerline_max_iterations
        ac.set_potential(dist_map)

        # find centerline starting from the ventral_side
        points = curves.translate_points(self.ventral_side, -offset[0],
                                         -offset[1])
        spacing = self.centerline_spacing
        points = curves.make_curve_equidistant(points, spacing=spacing)
        # use the active contour algorithm
        points = ac.find_contour(points)
        points = curves.make_curve_equidistant(points, spacing=spacing)
        # translate points back into global coordinate system
        points = curves.translate_points(points, offset[0], offset[1])

        # orient the centerline such that it starts at the posterior end
        dist1 = curves.point_distance(points[0], self.endpoints[0])
        dist2 = curves.point_distance(points[-1], self.endpoints[0])
        if dist1 > dist2:
            points = points[::-1]

        return points
示例#2
0
 def centerline(self):
     """ determine the center line of the tail """
     mask, offset = self.mask
     dist_map = cv2.distanceTransform(mask, cv2.DIST_L2, 5)
     
     # setup active contour algorithm
     ac = ActiveContour(blur_radius=self.centerline_blur_radius,
                        closed_loop=False,
                        alpha=0, #< line length is constraint by beta
                        beta=self.centerline_bending_stiffness,
                        gamma=self.centerline_adaptation_rate)
     ac.max_iterations = self.centerline_max_iterations
     ac.set_potential(dist_map)
     
     # find centerline starting from the ventral_side
     points = curves.translate_points(self.ventral_side,
                                      -offset[0], -offset[1])
     spacing = self.centerline_spacing
     points = curves.make_curve_equidistant(points, spacing=spacing)
     # use the active contour algorithm
     points = ac.find_contour(points)
     points = curves.make_curve_equidistant(points, spacing=spacing)
     # translate points back into global coordinate system
     points = curves.translate_points(points, offset[0], offset[1])
     
     # orient the centerline such that it starts at the posterior end
     dist1 = curves.point_distance(points[0], self.endpoints[0])
     dist2 = curves.point_distance(points[-1], self.endpoints[0])
     if dist1 > dist2:
         points = points[::-1]
     
     return points
 def energy_curvature(pos):
     """ part of the energy related to the curvature of the ground line """
     # get curvature part: http://en.wikipedia.org/wiki/Menger_curvature
     p_c = (p[0] + pos*dy, p[1] - pos*dx)
     a = curves.point_distance(p_p, p_c)
     b = curves.point_distance(p_c, p_n)
     c = curves.point_distance(p_n, p_p)
     
     # determine curvature of circle through the three points
     A = regions.triangle_area(a, b, c)
     curvature = 4*A/(a*b*c)*spacing
     # We don't scale by with the arc length a + b, because this 
     # would increase the curvature weight in situations where
     # fitting is complicated (close to burrow entries)
     return curvature_energy_factor*curvature
示例#4
0
    def extend_mouse_trail(self):
        """ extends the mouse trail using the current mouse position """
        ground_line = self.ground.linestring

        # remove points which are in front of the mouse
        if self.mouse_trail:
            spacing = self.params['mouse/model_radius']
            trail = np.array(self.mouse_trail)

            # get distance between the current point and the previous ones
            dist = np.hypot(trail[:, 0] - self.mouse_pos[0],
                            trail[:, 1] - self.mouse_pos[1])
            points_close = (dist < spacing)

            # delete obsolete points
            if np.any(points_close):
                i = np.nonzero(points_close)[0][0]
                del self.mouse_trail[i:]

        # check the two ends of the mouse trail
        if self.mouse_trail:
            # move first point to ground
            ground_point = curves.get_projection_point(ground_line,
                                                       self.mouse_trail[0])
            self.mouse_trail[0] = ground_point

            # check whether a separate point needs to be inserted
            p1, p2 = self.mouse_trail[-1], self.mouse_pos
            if curves.point_distance(p1, p2) > spacing:
                mid_point = (0.5 * (p1[0] + p2[0]), 0.5 * (p1[1] + p2[1]))
                self.mouse_trail.append(mid_point)

            # append the current point
            self.mouse_trail.append(self.mouse_pos)
            ground_dist = curves.curve_length(self.mouse_trail)

        else:
            # create a mouse trail if it is not too far from the ground
            # the latter can happen, when the mouse suddenly appears underground
            ground_point = curves.get_projection_point(ground_line,
                                                       self.mouse_pos)
            ground_dist = curves.point_distance(ground_point, self.mouse_pos)
            if ground_dist < self.params['mouse/speed_max']:
                self.mouse_trail = [ground_point, self.mouse_pos]

        return ground_dist
示例#5
0
    def extend_mouse_trail(self):
        """ extends the mouse trail using the current mouse position """
        ground_line = self.ground.linestring
        
        # remove points which are in front of the mouse
        if self.mouse_trail:
            spacing = self.params['mouse/model_radius']
            trail = np.array(self.mouse_trail)
            
            # get distance between the current point and the previous ones
            dist = np.hypot(trail[:, 0] - self.mouse_pos[0],
                            trail[:, 1] - self.mouse_pos[1])
            points_close = (dist < spacing)
                
            # delete obsolete points
            if np.any(points_close):
                i = np.nonzero(points_close)[0][0]
                del self.mouse_trail[i:]
           
        # check the two ends of the mouse trail
        if self.mouse_trail:
            # move first point to ground
            ground_point = curves.get_projection_point(ground_line,
                                                       self.mouse_trail[0])
            self.mouse_trail[0] = ground_point

            # check whether a separate point needs to be inserted
            p1, p2 = self.mouse_trail[-1], self.mouse_pos
            if curves.point_distance(p1, p2) > spacing:
                mid_point = (0.5*(p1[0] + p2[0]), 0.5*(p1[1] + p2[1]))
                self.mouse_trail.append(mid_point)

            # append the current point
            self.mouse_trail.append(self.mouse_pos)
            ground_dist = curves.curve_length(self.mouse_trail)
            
        else:
            # create a mouse trail if it is not too far from the ground
            # the latter can happen, when the mouse suddenly appears underground
            ground_point = curves.get_projection_point(ground_line, self.mouse_pos)
            ground_dist = curves.point_distance(ground_point, self.mouse_pos)
            if ground_dist < self.params['mouse/speed_max']:
                self.mouse_trail = [ground_point, self.mouse_pos]

        return ground_dist
示例#6
0
    def determine_endpoint_indices(self, tail_prev=None):
        """ locate the end points as contour points with maximal distance 
        The posterior point is returned first.
        If `tail_prev` is given, the end points are oriented in the same way as
        in the previous tail, otherwise they are assigned automatically.
        """
        # get the points which are farthest away from each other
        dist = distance.squareform(distance.pdist(self.contour))
        indices = np.unravel_index(np.argmax(dist), dist.shape)

        if tail_prev is None:
            # determine the mass of tissue to determine posterior end
            mass = []
            for k in indices:
                radius = self.endpoint_mass_radius
                endzone = geometry.Point(self.contour[k]).buffer(radius)
                poly = self.polygon.buffer(0)  #< clean the polygon
                mass.append(poly.intersection(endzone).area)

            # determine posterior end point by measuring the surrounding
            if mass[1] < mass[0]:
                indices = indices[::-1]

        else:
            # sort end points according to previous frame
            prev_p, prev_a = tail_prev.endpoints
            this_1 = self.contour[indices[0]]
            this_2 = self.contour[indices[1]]
            dist1 = curves.point_distance(this_1, prev_p) + \
                    curves.point_distance(this_2, prev_a)
            dist2 = curves.point_distance(this_1, prev_a) + \
                    curves.point_distance(this_2, prev_p)
            if dist2 < dist1:
                indices = indices[::-1]

        # save indices in cache
        self.persistence_data['endpoint_indices'] = indices
        return indices
示例#7
0
    def determine_endpoint_indices(self, tail_prev=None):
        """ locate the end points as contour points with maximal distance 
        The posterior point is returned first.
        If `tail_prev` is given, the end points are oriented in the same way as
        in the previous tail, otherwise they are assigned automatically.
        """
        # get the points which are farthest away from each other
        dist = distance.squareform(distance.pdist(self.contour))
        indices = np.unravel_index(np.argmax(dist), dist.shape)
        
        if tail_prev is None:
            # determine the mass of tissue to determine posterior end
            mass = []
            for k in indices:
                radius = self.endpoint_mass_radius
                endzone = geometry.Point(self.contour[k]).buffer(radius)
                poly = self.polygon.buffer(0) #< clean the polygon
                mass.append(poly.intersection(endzone).area)
                
            # determine posterior end point by measuring the surrounding
            if mass[1] < mass[0]:
                indices = indices[::-1]
               
        else:
            # sort end points according to previous frame
            prev_p, prev_a = tail_prev.endpoints
            this_1 = self.contour[indices[0]]
            this_2 = self.contour[indices[1]]
            dist1 = curves.point_distance(this_1, prev_p) + \
                    curves.point_distance(this_2, prev_a)
            dist2 = curves.point_distance(this_1, prev_a) + \
                    curves.point_distance(this_2, prev_p)
            if dist2 < dist1:
                indices = indices[::-1]

        # save indices in cache
        self.persistence_data['endpoint_indices'] = indices
        return indices        
示例#8
0
    def get_track_graph(self, tracks, threshold, highlight_nodes=None):
        """ builds a weighted, directed graph representing the possible trajectories """
        if highlight_nodes:
            highlight_nodes = set(highlight_nodes)
        else:
            highlight_nodes = set()
        
        graph = nx.DiGraph()
        
        # find all possible connections
        time_scale = self.params['tracking/time_scale']
        tolerated_overlap = self.params['tracking/tolerated_overlap']
        look_back_count = int(tolerated_overlap) + 5
        for a_idx, a in enumerate(tracks):
            # compare to other nodes (look back into past, too) 
            for b in tracks[max(a_idx - look_back_count, 0):]:
                if a is b or graph.has_edge(a, b):
                    continue # don't add self-loops or multiple loops
                gap_length = b.start - a.end #< time gap between the two chunks
                if gap_length > -tolerated_overlap:
                    # calculate the cost of this gap
                    # lower is better; all terms should be normalized to one
                    
                    distance = curves.point_distance(a.last.pos, b.first.pos)
                    
                    cost = (
                        #+ (2 - a.mouse_score - b.mouse_score)       # is it a mouse? 
                        + distance/self.params['mouse/speed_max']    # how close are the positions
                        + abs(gap_length)/time_scale                 # is it a long gap?
                    )

                    # add the edge if the cost is not too high
                    if cost < threshold:
                        graph.add_edge(a, b, cost=cost)
                        
            # highlight node if necessary
            if a in graph:
                graph.node[a]['highlight'] = (a_idx in highlight_nodes)

        return graph
    def _get_burrow_exit_length(self, burrow):        
        """ calculates the length of all exists of the given burrow """
        # identify all points that are close to the ground line
        dist_max = burrow.parameters['ground_point_distance']
        g_line = self.ground_line.linestring
        points = burrow.contour
        exitpoints = [g_line.distance(geometry.Point(point)) < dist_max
                      for point in points]
        
        # find the indices of contiguous true regions
        indices = math.contiguous_true_regions(exitpoints)

        # find the total length of all exits        
        exit_length = 0
        for a, b in indices:
            exit_length += curves.curve_length(points[a : b+1])
        
        # handle the first and last point if they both belong to an exit 
        if exitpoints[0] and exitpoints[-1]:
            exit_length += curves.point_distance(points[0], points[-1])
            
        return exit_length
示例#10
0
 def add_tracks_to_trajectory(self, tracks, trajectory):
     """ iterates through all tracks and adds them to the trajectory, using
     linear interpolation where necessary and appropriate """
     
     time_last, obj_last = None, None        
     for track in tracks:
         # check the connection between the previous point and the current one
         if obj_last is not None:
             time_now, obj_now = track.start, track.first
             time_gap = time_now - time_last
             small_gap = (time_gap < self.params['tracking/maximal_gap'])
             obj_dist = curves.point_distance(obj_last.pos, obj_now.pos)
             small_jump = (obj_dist < self.params['tracking/maximal_jump'])
             
             # check whether we should interpolate 
             if small_gap and small_jump:
                 frames = np.arange(time_last + 1, time_now)
                 ratio = np.linspace(0, 1, len(frames) + 2)[1:-1]
                 x1, x2 = obj_last.pos[0], obj_now.pos[0]
                 trajectory[frames, 0] = x1 + (x2 - x1)*ratio
                 y1, y2 = obj_last.pos[1], obj_now.pos[1]
                 trajectory[frames, 1] = y1 + (y2 - y1)*ratio
                 
             # check whether the tracking went wrong
             if time_gap <= 1 and not small_jump:
                 # make sure that there is a gap indicated by nan between
                 # the two chunks 
                 trajectory[time_now - 1:time_last + 1, :] = np.nan
         
         # add the data of this track directly
         for time, obj in track:
             if np.all(np.isfinite(trajectory[time, :])):
                 # average overlapping tracks
                 trajectory[time, :] = (trajectory[time, :] + obj.pos)/2
             else:
                 trajectory[time, :] = obj.pos
                 
         time_last, obj_last = time, obj
    def _get_burrow_exit_length(self, burrow):
        """ calculates the length of all exists of the given burrow """
        # identify all points that are close to the ground line
        dist_max = burrow.parameters['ground_point_distance']
        g_line = self.ground_line.linestring
        points = burrow.contour
        exitpoints = [
            g_line.distance(geometry.Point(point)) < dist_max
            for point in points
        ]

        # find the indices of contiguous true regions
        indices = math.contiguous_true_regions(exitpoints)

        # find the total length of all exits
        exit_length = 0
        for a, b in indices:
            exit_length += curves.curve_length(points[a:b + 1])

        # handle the first and last point if they both belong to an exit
        if exitpoints[0] and exitpoints[-1]:
            exit_length += curves.point_distance(points[0], points[-1])

        return exit_length
示例#12
0
    def adapt_tail_contours(self, tails):
        """ adapt tail contour to _frame, assuming that they are already close """
        # get the tails that we want to adapt
        if self.params['detection/every_frame']:
            tails_estimate = self.locate_tails_roughly(tails)
        else:
            tails_estimate = tails[:] #< copy list

        if len(tails_estimate) != len(tails):
            raise RuntimeError('Found %d instead of %d tails in this frame.' % 
                               (len(tails), len(tails_estimate)))
        
        # setup active contour algorithm
        ac = ActiveContour(blur_radius=self.params['contour/blur_radius'],
                           closed_loop=True,
                           alpha=self.params['contour/line_tension'],
                           beta=self.params['contour/bending_stiffness'],
                           gamma=self.params['contour/adaptation_rate'])
        ac.max_iterations = self.params['contour/max_iterations']
        #potential_approx = self.threshold_gradient_strength(gradient_mag)
        ac.set_potential(self.contour_potential)

#         debug.show_shape(*[t.contour for t in tails],
#                          background=self.contour_potential)        
        
        # get rectangle describing the interior of the _frame
        height, width = self._frame.shape
        region_center = shapes.Rectangle(0, 0, width, height)
        region_center.buffer(-self.params['contour/border_anchor_distance'])
        
        # iterate through all previous tails
        for k, tail_prev in enumerate(tails):
            # find the tail that is closest
            center = tail_prev.centroid
            idx = np.argmin([curves.point_distance(center, t.centroid)
                             for t in tails_estimate])
            
            # adapt this contour to the potential
            tail_estimate = tails_estimate.pop(idx)
            
            # determine the points close to the boundary that will be anchored
            # at their position, because there are no features to track at the
            # boundary 
            anchor_idx = ~region_center.points_inside(tail_estimate.contour)
            
            # disable anchoring for points at the posterior end of the tail
            ps = tail_estimate.contour[anchor_idx]
            dists = spatial.distance.cdist(ps, [tail_estimate.endpoints[0]])
            dist_threshold = self.params['contour/typical_width']
            anchor_idx[anchor_idx] = (dists.flat > dist_threshold)
            
            # use an active contour algorithm to adapt the contour points
            contour = ac.find_contour(tail_estimate.contour, anchor_idx,
                                      anchor_idx)
            logging.debug('Active contour took %d iterations.',
                          ac.info['iteration_count'])
            
            # update the old tail to keep the identity of sides
            tails[k] = Tail.create_similar(contour, tail_prev)
            
        return tails
示例#13
0
    def classify_mouse_track(self):
        """ classifies the mouse at all times """
        self.log_event('Pass 2 - Start classifying the mouse.')
        
        # load the mouse, the ground, and the burrows
        mouse_track = self.data['pass2/mouse_trajectory']
        ground_profile = self.data['pass2/ground_profile']
        burrow_tracks = self.data['pass1/burrows/tracks']
        
        # load some variables
        mouse_radius = self.params['mouse/model_radius']
        trail_spacing = self.params['burrows/centerline_segment_length']
        burrow_next_change = 0
        mouse_trail = None
        for frame_id, mouse_pos in enumerate(mouse_track.pos):
            if not np.all(np.isfinite(mouse_pos)):
                # the mouse position is invalid
                continue
            
            # initialize variables
            state = {}
                    
            # check the mouse position
            ground = ground_profile.get_ground_profile(frame_id)
            if ground is not None:
                if ground.above_ground(mouse_pos):
                    state['underground'] = False
                    if mouse_pos[1] + mouse_radius < ground.get_y(mouse_pos[0]):
                        state['location'] = 'air'
                    elif mouse_pos[1] < ground.midline:
                        state['location'] = 'hill'
                    else:
                        state['location'] = 'valley'

                    mouse_trail = None
                    # get index of the ground line
                    dist = np.linalg.norm(ground.points - mouse_pos[None, :], axis=1)
                    ground_idx = np.argmin(dist)
                    # get distance from ground line
                    ground_dist = ground.linestring.distance(geometry.Point(mouse_pos))

                else:
                    state['underground'] = True
                    # check the burrow structure
                    if frame_id >= burrow_next_change:
                        burrows, burrow_next_change = \
                            burrow_tracks.find_burrows(frame_id, ret_next_change=True)
                    
                    # check whether the mouse is inside a burrow
                    mouse_point = geometry.Point(mouse_pos)
                    for burrow in burrows:
                        if burrow.polygon.contains(mouse_point):
                            state['location'] = 'burrow'
                            break

                    # keep the ground index from last time
                    ground_idx = mouse_track.ground_idx[frame_id - 1]

                    # handle mouse trail
                    if mouse_trail is None:
                        # start a new mouse trail and initialize it with the                         
                        # ground point closest to the mouse       
                        mouse_prev = mouse_track.pos[frame_id - 1]              
                        ground_point = curves.get_projection_point(ground.linestring, mouse_prev)
                        mouse_trail = [ground_point, mouse_pos]

                    else:
                        # work with an existing mouse trail
                        p_trail = mouse_trail[-2]
                        
                        if curves.point_distance(p_trail, mouse_pos) < trail_spacing:
                            # old trail should be modified
                            if len(mouse_trail) > 2:
                                # check whether the trail has to be shortened
                                p_trail = mouse_trail[-3]
                                if curves.point_distance(p_trail, mouse_pos) < trail_spacing:
                                    del mouse_trail[-1] #< shorten trail
                                
                            mouse_trail[-1] = mouse_pos
                        else:
                            # old trail must be extended
                            mouse_trail.append(mouse_pos)
                        
                    # get distance the mouse is under ground
                    ground_dist = -curves.curve_length(mouse_trail)
                    
            # set the mouse state
            mouse_track.set_state(frame_id, state, ground_idx, ground_dist)
        
        self.log_event('Pass 2 - Finished classifying the mouse.')
示例#14
0
    def classify_mouse_state(self, mouse_track):
        """ classifies the mouse in the current _frame """
        if (not np.all(np.isfinite(self.mouse_pos)) or
            self.ground is None):
            
            # Not enough information to do anything
            self.mouse_trail = None
            return
        
        # initialize variables
        state = {}
        margin = self.params['mouse/model_radius']/2
        mouse_radius = self.params['mouse/model_radius']
        
        # check the horizontal position
        if self.mouse_pos[0] > self.background.shape[1]//2:
            state['position_horizontal'] = 'right'
        else:
            state['position_horizontal'] = 'left'
                
        # compare y value of mouse and ground (y-axis points down)
        if self.mouse_pos[1] > self.ground.get_y(self.mouse_pos[0]) + margin:
            # handle mouse trail
            ground_dist = self.extend_mouse_trail()
            
            # store the ground distance as a negative number 
            ground_dist *= -1 
            
            # score the burrow based on its entry point
            if self.ground_idx is None:
                # only necessary if mouse starts inside burrow
                dist = np.linalg.norm(self.ground.points - self.mouse_pos[None, :], axis=1)
                self.ground_idx = np.argmin(dist)
            entry_point = self.ground.points[self.ground_idx]
            if entry_point[1] > self.ground.midline:
                state['location'] = 'burrow'
            else:
                state['location'] = 'dimple'
                
            # check whether we are at the end of the burrow
            for burrow in self.burrows:
                dist = curves.point_distance(burrow.end_point, self.mouse_pos)
                if dist < mouse_radius:
                    state['location_detail'] = 'end point'
                    break
            else:
                state['location_detail'] = 'general'

        else: 
            if self.mouse_pos[1] + 2*mouse_radius < self.ground.get_y(self.mouse_pos[0]):
                state['location'] = 'air'
            elif self.mouse_pos[1] < self.ground.midline:
                state['location'] = 'hill'
            else:
                state['location'] = 'valley'
            state['location_detail'] = 'general'

            # get index of the ground line
            dist = np.linalg.norm(self.ground.points - self.mouse_pos[None, :], axis=1)
            self.ground_idx = np.argmin(dist)
            # get distance from ground line
            mouse_point = geometry.Point(self.mouse_pos)
            ground_dist = self.ground.linestring.distance(mouse_point)
            # report the distance as negative, if the mouse is under the ground line
            if self.mouse_pos[1] > self.ground.get_y(self.mouse_pos[0]):
                ground_dist *= -1
            
            # reset the mouse trail since the mouse is over the ground
            self.mouse_trail = None
            
        # determine whether the mouse is moving or not
        velocity = self.data['pass2/mouse_trajectory'].velocity[self.frame_id, :]
        speed = np.hypot(velocity[0], velocity[1])
        if speed > self.params['mouse/moving_threshold_pixel_frame']:
            state['dynamics'] = 'moving'
        else:
            state['dynamics'] = 'stationary'
            
        # set the mouse state
        mouse_track.set_state(self.frame_id, state, self.ground_idx, ground_dist)
示例#15
0
    def classify_mouse_state(self, mouse_track):
        """ classifies the mouse in the current _frame """
        if (not np.all(np.isfinite(self.mouse_pos)) or self.ground is None):

            # Not enough information to do anything
            self.mouse_trail = None
            return

        # initialize variables
        state = {}
        margin = self.params['mouse/model_radius'] / 2
        mouse_radius = self.params['mouse/model_radius']

        # check the horizontal position
        if self.mouse_pos[0] > self.background.shape[1] // 2:
            state['position_horizontal'] = 'right'
        else:
            state['position_horizontal'] = 'left'

        # compare y value of mouse and ground (y-axis points down)
        if self.mouse_pos[1] > self.ground.get_y(self.mouse_pos[0]) + margin:
            # handle mouse trail
            ground_dist = self.extend_mouse_trail()

            # store the ground distance as a negative number
            ground_dist *= -1

            # score the burrow based on its entry point
            if self.ground_idx is None:
                # only necessary if mouse starts inside burrow
                dist = np.linalg.norm(self.ground.points -
                                      self.mouse_pos[None, :],
                                      axis=1)
                self.ground_idx = np.argmin(dist)
            entry_point = self.ground.points[self.ground_idx]
            if entry_point[1] > self.ground.midline:
                state['location'] = 'burrow'
            else:
                state['location'] = 'dimple'

            # check whether we are at the end of the burrow
            for burrow in self.burrows:
                dist = curves.point_distance(burrow.end_point, self.mouse_pos)
                if dist < mouse_radius:
                    state['location_detail'] = 'end point'
                    break
            else:
                state['location_detail'] = 'general'

        else:
            if self.mouse_pos[1] + 2 * mouse_radius < self.ground.get_y(
                    self.mouse_pos[0]):
                state['location'] = 'air'
            elif self.mouse_pos[1] < self.ground.midline:
                state['location'] = 'hill'
            else:
                state['location'] = 'valley'
            state['location_detail'] = 'general'

            # get index of the ground line
            dist = np.linalg.norm(self.ground.points - self.mouse_pos[None, :],
                                  axis=1)
            self.ground_idx = np.argmin(dist)
            # get distance from ground line
            mouse_point = geometry.Point(self.mouse_pos)
            ground_dist = self.ground.linestring.distance(mouse_point)
            # report the distance as negative, if the mouse is under the ground line
            if self.mouse_pos[1] > self.ground.get_y(self.mouse_pos[0]):
                ground_dist *= -1

            # reset the mouse trail since the mouse is over the ground
            self.mouse_trail = None

        # determine whether the mouse is moving or not
        velocity = self.data['pass2/mouse_trajectory'].velocity[
            self.frame_id, :]
        speed = np.hypot(velocity[0], velocity[1])
        if speed > self.params['mouse/moving_threshold_pixel_frame']:
            state['dynamics'] = 'moving'
        else:
            state['dynamics'] = 'stationary'

        # set the mouse state
        mouse_track.set_state(self.frame_id, state, self.ground_idx,
                              ground_dist)