def _direct_conn_to_ground(point, has_offset=False): """ helper function checking the connection to the ground """ if has_offset: point = (point[0] + offset[0], point[1] + offset[1]) p_ground = curves.get_projection_point(outside, point) conn_line = geometry.LineString([point, p_ground]) return conn_line.length < 1 or conn_line.within(burrow_poly)
def get_measurement_lines(self, tail): """ determines the measurement segments that are used for the line scan """ f_c = self.params['measurement/line_offset'] f_o = 1 - f_c centerline = tail.centerline result = [] for side in tail.sides: # find the line between the centerline and the ventral line points = [] for p_c in centerline: p_o = curves.get_projection_point(side, p_c) #< outer line points.append((f_c*p_c[0] + f_o*p_o[0], f_c*p_c[1] + f_o*p_o[1])) # do spline fitting to smooth the line smoothing = self.params['measurement/spline_smoothing']*len(points) tck, _ = interpolate.splprep(np.transpose(points), k=2, s=smoothing) points = interpolate.splev(np.linspace(-0.5, .8, 100), tck) points = zip(*points) #< transpose list # restrict centerline to object mline = geometry.LineString(points).intersection(tail.polygon) # pick longest line if there are many due to strange geometries if isinstance(mline, geometry.MultiLineString): mline = mline[np.argmax([l.length for l in mline])] result.append(np.array(mline.coords)) return result
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
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
def calculate_burrow_centerline(self, burrow, point_start=None): """ determine the centerline of a burrow with one exit """ if point_start is None: point_start = burrow.centerline[0] # get a binary image of the burrow mask, shift = burrow.get_mask(margin=2, dtype=np.int32, ret_offset=True) # move starting point onto ground line ground_line = self.ground.linestring point_start = curves.get_projection_point(ground_line, point_start) point_start = (int(point_start[0]) - shift[0], int(point_start[1]) - shift[1]) mask[point_start[1], point_start[0]] = 1 # calculate the distance from the start point regions.make_distance_map(mask, [point_start]) # find the second point by locating the farthest point _, _, _, p_end = cv2.minMaxLoc(mask) # find an estimate for the centerline from the shortest distance from # the end point to the burrow exit points = regions.shortest_path_in_distance_map(mask, p_end) # translate the points back to global coordinates centerline = curves.translate_points(points, shift[0], shift[1]) # save centerline such that burrow exit is first point centerline = centerline[::-1] # add points that might be outside of the burrow contour ground_start = curves.get_projection_point(ground_line, centerline[0]) centerline.insert(0, ground_start) # simplify the curve centerline = cv2.approxPolyDP(np.array(centerline, np.int), epsilon=1, closed=False) # save the centerline in the burrow structure burrow.centerline = centerline[:, 0, :]
def calculate_burrow_centerline(self, burrow, point_start=None): """ determine the centerline of a burrow with one exit """ if point_start is None: point_start = burrow.centerline[0] # get a binary image of the burrow mask, shift = burrow.get_mask(margin=2, dtype=np.int32, ret_offset=True) # move starting point onto ground line ground_line = self.ground.linestring point_start = curves.get_projection_point(ground_line, point_start) point_start = (int(point_start[0]) - shift[0], int(point_start[1]) - shift[1]) mask[point_start[1], point_start[0]] = 1 # calculate the distance from the start point regions.make_distance_map(mask, [point_start]) # find the second point by locating the farthest point _, _, _, p_end = cv2.minMaxLoc(mask) # find an estimate for the centerline from the shortest distance from # the end point to the burrow exit points = regions.shortest_path_in_distance_map(mask, p_end) # translate the points back to global coordinates centerline = curves.translate_points(points, shift[0], shift[1]) # save centerline such that burrow exit is first point centerline = centerline[::-1] # add points that might be outside of the burrow contour ground_start = curves.get_projection_point(ground_line, centerline[0]) if isinstance(centerline, np.ndarray): centerline = np.insert(centerline, 0, ground_start).reshape(-1, 2) else: centerline.insert(0, ground_start) # simplify the curve centerline = cv2.approxPolyDP(np.array(centerline, np.int), epsilon=1, closed=False) # save the centerline in the burrow structure burrow.centerline = centerline[:, 0, :]
def _connect_burrow_to_structure(self, contour, structure): """ extends the burrow contour such that it connects to the ground line or to other burrows """ outline = geometry.Polygon(contour) # determine burrow points close to the structure dist = structure.distance(outline) conn_points = [] while len(conn_points) == 0: dist += self.params['burrows/width']/2 conn_points = [point for point in contour if structure.distance(geometry.Point(point)) < dist] conn_points = np.array(conn_points) # cluster the points to detect multiple connections # this is important when a burrow has multiple exits to the ground if len(conn_points) >= 2: dist_max = self.params['burrows/width'] data = cluster.hierarchy.fclusterdata(conn_points, dist_max, method='single', criterion='distance') else: data = np.ones(1, np.int) burrow_width_min = self.params['burrows/width_min'] for cluster_id in np.unique(data): p_exit = conn_points[data == cluster_id].mean(axis=0) p_ground = curves.get_projection_point(structure, p_exit) line = geometry.LineString((p_exit, p_ground)) tunnel = line.buffer(distance=burrow_width_min/2, cap_style=geometry.CAP_STYLE.flat) # add this to the burrow outline outline = outline.union(tunnel.buffer(0.1)) # get the contour points outline = regions.get_enclosing_outline(outline) outline = regions.regularize_linear_ring(outline) contour = np.array(outline.coords) # fill the burrow mask, such that this extension does not have to be # done next time again cv2.fillPoly(self.burrow_mask, [np.asarray(contour, np.int32)], 1) return contour
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.')
def store_burrows(self): """ associates the current burrows with burrow tracks """ burrow_tracks = self.result['burrows/tracks'] ground_polygon = geometry.Polygon(self.get_ground_polygon_points()) # check whether we already know this burrow # the burrows in self.burrows will always be larger than the burrows # in self.active_burrows. Consequently, it can happen that a current # burrow overlaps two older burrows, but the reverse cannot be true for burrow in self.burrows: # find all tracks to which this burrow may belong track_ids = [track_id for track_id, burrow_last in self.active_burrows() if burrow_last.intersects(burrow)] if len(track_ids) > 1: # merge all burrows to a single track and keep the largest one track_longest, length_max = None, 0 for track_id in track_ids: burrow_last = burrow_tracks[track_id].last # find track with longest burrow if burrow_last.length > length_max: track_longest, length_max = track_id, burrow_last.length # merge the burrows burrow.merge(burrow_last) # keep the burrow parts that are below the ground line try: polygon = burrow.polygon.intersection(ground_polygon) except geos.TopologicalError: continue if polygon.is_empty: continue try: burrow.contour = regions.get_enclosing_outline(polygon) except TypeError: # can occur in corner cases where the enclosing outline cannot # be found continue # make sure that the burrow centerline lies within the ground region ground_poly = geometry.Polygon(self.get_ground_polygon_points()) if burrow.linestring.length > 0: line = burrow.linestring.intersection(ground_poly) else: line = None if isinstance(line, geometry.multilinestring.MultiLineString): # pick the longest line if there are multiple index_longest = np.argmax(l.length for l in line) line = line[index_longest] is_line = isinstance(line, geometry.linestring.LineString) if not is_line or line.is_empty or line.length <= 1: # the centerline disappeared # => calculate a new centerline from the burrow contour end_point = self.burrow_estimate_exit(burrow)[0] self.calculate_burrow_centerline(burrow, point_start=end_point) else: # adjust the burrow centerline to reach to the ground line # it could be that the whole line was underground # => move the first data point onto the ground line line = np.array(line, np.double) line[0] = curves.get_projection_point(self.ground.linestring, line[0]) # set the updated burrow centerline burrow.centerline = line # store the burrow if it is valid if burrow.is_valid: if len(track_ids) > 1: # add the burrow to the longest track burrow_tracks[track_longest].append(self.frame_id, burrow) elif len(track_ids) == 1: # add the burrow to the matching track burrow_tracks[track_ids[0]].append(self.frame_id, burrow) else: # create the burrow track burrow_track = BurrowTrack(self.frame_id, burrow) burrow_tracks.append(burrow_track) # use the new set of burrows in the next iterations self.burrows = [b.copy() for _, b in self.active_burrows(time_interval=0)]
def store_burrows(self): """ associates the current burrows with burrow tracks """ burrow_tracks = self.result['burrows/tracks'] ground_polygon = geometry.Polygon(self.get_ground_polygon_points()) # check whether we already know this burrow # the burrows in self.burrows will always be larger than the burrows # in self.active_burrows. Consequently, it can happen that a current # burrow overlaps two older burrows, but the reverse cannot be true for burrow in self.burrows: # find all tracks to which this burrow may belong track_ids = [ track_id for track_id, burrow_last in self.active_burrows() if burrow_last.intersects(burrow) ] if len(track_ids) > 1: # merge all burrows to a single track and keep the largest one track_longest, length_max = None, 0 for track_id in track_ids: burrow_last = burrow_tracks[track_id].last # find track with longest burrow if burrow_last.length > length_max: track_longest, length_max = track_id, burrow_last.length # merge the burrows burrow.merge(burrow_last) # keep the burrow parts that are below the ground line try: polygon = burrow.polygon.intersection(ground_polygon) except geos.TopologicalError: continue if polygon.is_empty: continue burrow.contour = regions.get_enclosing_outline(polygon) # make sure that the burrow centerline lies within the ground region ground_poly = geometry.Polygon(self.get_ground_polygon_points()) line = burrow.linestring.intersection(ground_poly) if isinstance(line, geometry.multilinestring.MultiLineString): # pick the longest line if there are multiple index_longest = np.argmax(l.length for l in line) line = line[index_longest] is_line = isinstance(line, geometry.linestring.LineString) if not is_line or line.is_empty or line.length <= 1: # the centerline disappeared # => calculate a new centerline from the burrow contour end_point = self.burrow_estimate_exit(burrow)[0] self.calculate_burrow_centerline(burrow, point_start=end_point) else: # adjust the burrow centerline to reach to the ground line # it could be that the whole line was underground # => move the first data point onto the ground line line = np.array(line, np.double) line[0] = curves.get_projection_point(self.ground.linestring, line[0]) # set the updated burrow centerline burrow.centerline = line # store the burrow if it is valid if burrow.is_valid: if len(track_ids) > 1: # add the burrow to the longest track burrow_tracks[track_longest].append(self.frame_id, burrow) elif len(track_ids) == 1: # add the burrow to the matching track burrow_tracks[track_ids[0]].append(self.frame_id, burrow) else: # create the burrow track burrow_track = BurrowTrack(self.frame_id, burrow) burrow_tracks.append(burrow_track) # use the new set of burrows in the next iterations self.burrows = [ b.copy() for _, b in self.active_burrows(time_interval=0) ]
def _get_burrow_centerline(self, burrow, points_start, points_end=None): """ determine the centerline of a burrow with one exit """ ground_line = self.ground.linestring # get a binary image of the burrow mask, shift = burrow.get_mask(margin=2, dtype=np.int32, ret_offset=True) # mark the start points according to their distance to the ground line # dists_g = [ground_line.distance(geometry.Point(p)) # for p in points_start] points_start = curves.translate_points(points_start, -shift[0], -shift[1]) for p in points_start: mask[p[1], p[0]] = 1 if points_end is None: # end point is not given and will thus be determined automatically # calculate the distance from the start point regions.make_distance_map(mask.T, points_start) # find the second point by locating the farthest point _, _, _, p_end = cv2.minMaxLoc(mask) else: # prepare the end point if present # translate that point to the mask _frame points_end = curves.translate_points(points_end, -shift[0], -shift[1]) for p in points_end: mask[p[1], p[0]] = 1 # calculate the distance from the start point regions.make_distance_map(mask.T, points_start, points_end) # get the distance between the start and the end point dists = [mask[p[1], p[0]] for p in points_end] best_endpoint = np.argmin(dists) p_end = points_end[best_endpoint] # find an estimate for the centerline from the shortest distance from # the end point to the burrow exit points = regions.shortest_path_in_distance_map(mask, p_end) # debug.show_shape(geometry.MultiPoint(points_start), # geometry.Point(p_end), # background=mask) # exit() # translate the points back to global coordinates centerline = curves.translate_points(points, shift[0], shift[1]) # save centerline such that burrow exit is first point centerline = centerline[::-1] # add points that might be outside of the burrow contour ground_start = curves.get_projection_point(ground_line, centerline[0]) centerline.insert(0, ground_start) if points_end is not None: ground_end = curves.get_projection_point(ground_line, centerline[-1]) centerline.append(ground_end) # simplify the curve centerline = cv2.approxPolyDP(np.array(centerline, np.int), epsilon=1, closed=False) # save the centerline in the burrow structure burrow.centerline = centerline[:, 0, :]