def locate_tails_roughly(self, tails=None): """ locate tail objects using thresholding """ # find features, using annotations in the first _frame use_annotations = (self.frame_id == self.frame_start) labels, _ = self.get_features(tails, use_annotations=use_annotations) # find the contours of these features contours = cv2.findContours(labels, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1] # locate the tails using these contours tails = [] for contour in contours: if cv2.contourArea(contour) > self.params['detection/area_min']: points = contour[:, 0, :] threshold = 0.002 * curves.curve_length(points) points = curves.simplify_curve(points, threshold) tails.append(Tail(points)) # debug.show_shape(*[t.contour_ring for t in tails], background=self._frame, # wait_for_key=False) logging.debug('Found %d tail(s) in _frame %d', len(tails), self.frame_id) return tails
def get_statistics(self): """ returns statistics for all the polygons """ result = {'name': self.name} # save results about ground line points = self.ground_line.points ground_width_px = abs(points[0, 0] - points[-1, 0]) ground_cm_per_pixel = self.params['cage/width_norm'] / ground_width_px result['ground'] = {'ground_length': self.ground_line.length, 'ground_width_pixel': ground_width_px, 'cm_per_pixel': ground_cm_per_pixel} # check the scale bar if self.scale_bar: logging.info('Found %d pixel long scale bar' % self.scale_bar.size) cm_per_pixel = self.params['scale_bar/length_cm']/self.scale_bar.size units = pint.UnitRegistry() scale_factor = cm_per_pixel * units.cm # check the ground line points = self.ground_line.points len_x_cm = abs(points[0, 0] - points[-1, 0]) * scale_factor w_min = self.params['cage/width_min'] * units.cm w_max = self.params['cage/width_max'] * units.cm if not w_min < len_x_cm < w_max: raise RuntimeError('The length (%s cm) of the ground line is ' 'off.' % len_x_cm) result['scale_bar'] = {'length_pixel': self.scale_bar.size, 'cm_per_pixel': cm_per_pixel} else: scale_factor = 1 result['scale_bar'] = None # collect result of all burrows result['burrows'] = [] for burrow in self.burrows: # calculate some additional statistics perimeter_exit = self._get_burrow_exit_length(burrow) exit_count = sum(1 for ep in burrow.endpoints if ep.is_exit) branch_length = sum(curves.curve_length(points) for points in burrow.branches) #graph = burrow.morphological_graph data = {'pos_x': burrow.centroid[0] * scale_factor, 'pos_y': burrow.centroid[1] * scale_factor, 'area': burrow.area * scale_factor**2, 'length': burrow.length * scale_factor, 'length_upwards': burrow.length_upwards * scale_factor, 'fraction_upwards': burrow.length_upwards / burrow.length, 'exit_count': exit_count, 'branch_length': branch_length * scale_factor, 'branch_count': len(burrow.branches), 'total_length': (branch_length + burrow.length)*scale_factor, 'perimeter': burrow.perimeter * scale_factor, 'perimeter_exit': perimeter_exit * scale_factor, 'openness': perimeter_exit / burrow.perimeter} result['burrows'].append(data) return result
def centerline(self, points): """ set the new centerline """ if points is None: self._centerline = None else: self._centerline = np.array(points, np.double) self.length = curves.curve_length(self._centerline) self._cache = {}
def get_mouse_distance(self): """ returns the total distance traveled by the mouse """ trajectory = self.get_mouse_track_data('trajectory_smoothed') # calculate distance valid = np.isfinite(trajectory[:, 0]) distance = curves.curve_length(trajectory[valid, :]) return distance
def determine_centerline(self): """ determines the centerline """ spacing = self.parameters['centerline_segment_length'] skip_length = self.parameters['centerline_skip_length'] endpoints = [p.coords for p in self.endpoints] centerline = self.get_centerline_smoothed(spacing=spacing, skip_length=skip_length, endpoints=endpoints) self._centerline = np.asarray(centerline) self.length = curves.curve_length(self._centerline)
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 find_sure_mouse_tracks(self, tracks): """ identifies tracks which surely describe mice. These tracks can then be used to split up the connection problem into distinct parts """ smoothing_window = self.params['tracking/position_smoothing_window'] dist_threshold = self.params['tracking/mouse_distance_threshold'] min_mean_speed = self.params['tracking/mouse_min_mean_speed'] max_speed = self.params['mouse/speed_max'] min_length = int(dist_threshold/(0.25 * max_speed)) sure_tracks = [] for track_id, track in enumerate(tracks): if len(track) < min_length: continue # get smoothed trajectory trajectory = track.get_trajectory(smoothing_window) dist = curves.curve_length(trajectory) if dist > dist_threshold and dist/len(track) > min_mean_speed: sure_tracks.append(track_id) # remove overlapping tracks that were marked as true tracks if sure_tracks: k = 1 t1 = tracks[sure_tracks[0]] while k < len(sure_tracks): t2 = tracks[sure_tracks[k]] if t1.end > t2.start: # overlap => delete shorter track if t1.duration > t2.duration: del sure_tracks[k] else: del sure_tracks[k-1] t1 = t2 else: # check next track k += 1 t1 = t2 return sure_tracks
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
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
def get_statistics(self): """ returns statistics for all the polygons """ result = {'name': self.name} # save results about ground line points = self.ground_line.points ground_width_px = abs(points[0, 0] - points[-1, 0]) ground_cm_per_pixel = self.params['cage/width_norm'] / ground_width_px result['ground'] = {'ground_length': self.ground_line.length, 'ground_width_pixel': ground_width_px, 'cm_per_pixel': ground_cm_per_pixel} # check the scale bar if self.scale_bar: logging.info('Found %d pixel long scale bar' % self.scale_bar.size) cm_per_pixel = self.params['scale_bar/length_cm'] / self.scale_bar.size units = pint.UnitRegistry() scale_factor = cm_per_pixel * units.cm # check the ground line points = self.ground_line.points len_x_cm = abs(points[0, 0] - points[-1, 0]) * scale_factor w_min = self.params['cage/width_min'] * units.cm w_max = self.params['cage/width_max'] * units.cm if not w_min < len_x_cm < w_max: raise RuntimeError('The length (%s) of the ground line is ' 'not in [%s, %s] for image `%s`.' % (len_x_cm, w_min, w_max, self.name)) result['scale_bar'] = {'length_pixel': self.scale_bar.size, 'cm_per_pixel': cm_per_pixel} else: # there is no scale bar scale_factor = 1 result['scale_bar'] = None logging.warn('Could not find a scale bar in the image `%s`', self.name) # determine some parameters try: angle_dist_px = (self.params['burrow/angle_measurement_distance_cm'] / cm_per_pixel) except UnboundLocalError: angle_dist_px = self.params['burrow/angle_measurement_distance_cm'] # collect result of all burrows result['burrows'] = [] for burrow in self.burrows: # calculate some additional statistics perimeter_exit = self._get_burrow_exit_length(burrow) exit_count = sum(1 for ep in burrow.endpoints if ep.is_exit) branch_length = sum(curves.curve_length(points) for points in burrow.branches) # determine burrow angles and distance between end points angle1 = np.rad2deg(burrow.get_entry_angle(angle_dist_px)) angle2 = np.rad2deg(burrow.get_exit_angle(angle_dist_px)) level_difference = (burrow.centerline[0, 1] - burrow.centerline[-1, 1]) # make sure the values are reported correctly if self._centerline_is_oriented(burrow): angle_entrance, angle_exit = angle1, angle2 else: # switch the measurements as if the centerline was reoriented angle_entrance, angle_exit = angle2, angle1 level_difference *= -1 # get the branch angles, measured at the point on the centerline branch_angles = [] for branch in burrow.branches: p0, p1 = branch[-1], branch[0] # calculate the angle, note that y-axis points down angle = np.arctan2(p0[1] - p1[1], p1[0] - p0[0]) branch_angles.append(np.rad2deg(angle)) while len(branch_angles) < 2: branch_angles.append(np.nan) #graph = burrow.morphological_graph data = {'pos_x': burrow.centroid[0] * scale_factor, 'pos_y': burrow.centroid[1] * scale_factor, 'area': burrow.area * scale_factor**2, 'length': burrow.length * scale_factor, 'length_upwards': burrow.length_upwards * scale_factor, 'fraction_upwards': burrow.length_upwards / burrow.length, 'exit_count': exit_count, 'entrance_angle [degree]': angle_entrance, 'exit_angle [degree]': angle_exit, 'entrance_exit_difference': level_difference * scale_factor, # 'centerline_oriented': self._centerline_is_oriented(burrow), 'branch_length': branch_length * scale_factor, 'branch_count': len(burrow.branches), 'branch_angle_1 [degree]': branch_angles[0], 'branch_angle_2 [degree]': branch_angles[1], 'total_length': (branch_length + burrow.length)*scale_factor, 'perimeter': burrow.perimeter * scale_factor, 'perimeter_exit': perimeter_exit * scale_factor, 'openness': perimeter_exit / burrow.perimeter} result['burrows'].append(data) return result
def is_moving(self): """ return if the object has moved in the last frames """ dist = curves.curve_length(self.get_track(-self.moving_window_frames, None)) return dist > self.moving_threshold_pixel
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 length(self): """ returns the length of the profile """ return curves.curve_length(self.points)
def is_moving(self): """ return if the object has moved in the last frames """ dist = curves.curve_length( self.get_track(-self.moving_window_frames, None)) return dist > self.moving_threshold_pixel
def get_statistics_periods(self, keys=None, slice_length=None): """ calculate statistics given in `keys` for consecutive time slices. If `keys` is None, all statistics are calculated. If `slice_length` is None, the statistics are calculated for the entire video. """ if keys is None: keys = OmniContainer() # determine the frame slices frame_range = self.get_frame_range() if slice_length: frame_range = range(frame_range[0], frame_range[1], int(slice_length)) frame_ivals = [ (a, b + 1) for a, b in itertools.izip(frame_range, frame_range[1:]) ] frame_slices = [slice(a, b + 1) for a, b in frame_ivals] # save the time slices used for analysis result = { 'frame_interval': frame_ivals, 'period_start': [a * self.time_scale for a, _ in frame_ivals], 'period_end': [b * self.time_scale for _, b in frame_ivals], 'period_duration': [(b - a) * self.time_scale for a, b in frame_ivals] } # get the area changes of the ground line if 'ground_removed' in keys or 'ground_accrued' in keys: area_removed, area_accrued = [], [] for f in frame_slices: poly_rem, poly_acc = self.get_ground_changes((f.start, f.stop)) area_removed.append(poly_rem.area) area_accrued.append(poly_acc.area) if 'ground_removed' in keys: result['ground_removed'] = area_removed * self.length_scale**2 if 'ground_accrued' in keys: result['ground_accrued'] = area_accrued * self.length_scale**2 # get durations of the mouse being in different states for key, pattern in (('time_spent_moving', '...M'), ('time_at_burrow_end', '.(B|D)E.')): # special case in which the calculation has to be done c = (key == 'time_at_burrow_end' and 'mouse_digging_rate' in keys) # alternatively, the computation might be requested directly if c or key in keys: states = self.get_mouse_state_vector([pattern]) duration = [ np.count_nonzero(states[t_slice] == 0) for t_slice in frame_slices ] result[key] = duration * self.time_scale # get velocity statistics speed_statistics = { 'mouse_speed_mean': lambda x: np.nan_to_num(np.array(x)).mean(), 'mouse_speed_mean_valid': lambda x: np.nanmean(x), 'mouse_speed_max': lambda x: np.nanmax(x) } if any(key in keys for key in speed_statistics.keys()): velocities = self.get_mouse_velocities() speed = np.hypot(velocities[:, 0], velocities[:, 1]) for key, stat_func in speed_statistics.iteritems(): res = [stat_func(speed[t_slice]) for t_slice in frame_slices] result[key] = np.array(res) * self.speed_scale # get distance statistics if 'mouse_distance_covered' in keys: trajectory = self.get_mouse_trajectory() dist = [] for t_slice in frame_slices: trajectory_part = trajectory[t_slice] valid = np.isfinite(trajectory_part[:, 0]) dist.append(curves.curve_length(trajectory_part[valid])) result['mouse_distance_covered'] = dist * self.length_scale if 'mouse_trail_longest' in keys: ground_dist = self.get_mouse_track_data('ground_dist') dist = [ -np.nanmin(ground_dist[t_slice]) for t_slice in frame_slices ] result['mouse_trail_longest'] = dist * self.length_scale if 'mouse_deepest_diagonal' in keys or 'mouse_deepest_vertical' in keys: dist_diag, dist_vert = self.get_mouse_ground_distance_max( frame_ivals) if 'mouse_deepest_diagonal' in keys: result[ 'mouse_deepest_diagonal'] = dist_diag * self.length_scale if 'mouse_deepest_vertical' in keys: result[ 'mouse_deepest_vertical'] = dist_vert * self.length_scale # get statistics about the burrow evolution if any(key in keys for key in ('burrow_area_excavated', 'mouse_digging_rate', 'time_burrow_grew')): stats = [ self.get_burrow_growth_statistics((f.start, f.stop)) for f in frame_slices ] stats = np.array(stats) if 'burrow_area_excavated' in keys or 'mouse_digging_rate' in keys: result[ 'burrow_area_excavated'] = stats[:, 0] * self.length_scale**2 if 'time_burrow_grew' in keys: result['time_burrow_grew'] = stats[:, 1] * self.time_scale # calculate the digging rate by considering burrows and the mouse if 'mouse_digging_rate' in keys: time_min = self.params['mouse/digging_rate_time_min'] if self.use_units: time_min *= self.time_scale unit_rate = self.length_scale**2 / self.time_scale area_min = 0 * self.length_scale**2 else: unit_rate = 1 area_min = 0 # calculate the digging rate digging_rate = [] for area, time in itertools.izip(result['burrow_area_excavated'], result['time_at_burrow_end']): if area > area_min and time > time_min: digging_rate.append(area / time) else: digging_rate.append(np.nan * unit_rate) result['mouse_digging_rate'] = digging_rate # determine the remaining keys if not isinstance(keys, OmniContainer): keys = set(keys) - set(result.keys()) if not isinstance(keys, OmniContainer): keys = set(keys) - set(result.keys()) if keys: # report statistics that could not be calculated self.logger.warn( 'The following statistics are not defined in ' 'the algorithm and could therefore not be ' 'calculated: %s', ', '.join(keys)) return result
def get_statistics_periods(self, keys=None, slice_length=None): """ calculate statistics given in `keys` for consecutive time slices. If `keys` is None, all statistics are calculated. If `slice_length` is None, the statistics are calculated for the entire video. """ if keys is None: keys = OmniContainer() # determine the frame slices frame_range = self.get_frame_range() if slice_length: frame_range = range(frame_range[0], frame_range[1], int(slice_length)) frame_ivals = [(a, b + 1) for a, b in itertools.izip(frame_range, frame_range[1:])] frame_slices = [slice(a, b + 1) for a, b in frame_ivals] # length of the periods period_durations = (np.array([(b - a + 1) for a, b in frame_ivals]) * self.time_scale) # save the time slices used for analysis result = {'frame_interval': frame_ivals, 'period_start': [a*self.time_scale for a, _ in frame_ivals], 'period_end': [b*self.time_scale for _, b in frame_ivals], 'period_duration': [(b - a)*self.time_scale for a, b in frame_ivals]} # get the area changes of the ground line if 'ground_removed' in keys or 'ground_accrued' in keys: area_removed, area_accrued = [], [] for f in frame_slices: poly_rem, poly_acc = self.get_ground_changes((f.start, f.stop)) area_removed.append(poly_rem.area) area_accrued.append(poly_acc.area) if 'ground_removed' in keys: result['ground_removed'] = area_removed * self.length_scale**2 if 'ground_accrued' in keys: result['ground_accrued'] = area_accrued * self.length_scale**2 # get durations of the mouse being in different states for key, pattern in (('time_spent_moving', '...M'), ('time_at_burrow_end', '.(B|D)E.')): # special case in which the calculation has to be done c = (key == 'time_at_burrow_end' and 'mouse_digging_rate' in keys) # alternatively, the computation might be requested directly if c or key in keys: states = self.get_mouse_state_vector([pattern]) duration = [np.count_nonzero(states[t_slice] == 0) for t_slice in frame_slices] result[key] = duration * self.time_scale key_fraction = key.replace('time_', 'fraction_') result[key_fraction] = np.array(result[key]) / period_durations # get velocity statistics speed_statistics = { 'mouse_speed_mean': lambda x: np.nan_to_num(np.array(x)).mean(), 'mouse_speed_mean_valid': lambda x: np.nanmean(x), 'mouse_speed_max': lambda x: np.nanmax(x) } if any(key in keys for key in speed_statistics.keys()): velocities = self.get_mouse_velocities() speed = np.hypot(velocities[:, 0], velocities[:, 1]) for key, stat_func in speed_statistics.iteritems(): res = [stat_func(speed[t_slice]) for t_slice in frame_slices] result[key] = np.array(res) * self.speed_scale # get distance statistics if 'mouse_distance_covered' in keys: trajectory = self.get_mouse_trajectory() dist = [] for t_slice in frame_slices: trajectory_part = trajectory[t_slice] valid = np.isfinite(trajectory_part[:, 0]) dist.append(curves.curve_length(trajectory_part[valid])) result['mouse_distance_covered'] = dist * self.length_scale if 'mouse_trail_longest' in keys: ground_dist = self.get_mouse_track_data('ground_dist') dist = [-np.nanmin(ground_dist[t_slice]) for t_slice in frame_slices] result['mouse_trail_longest'] = dist * self.length_scale if 'mouse_deepest_diagonal' in keys or 'mouse_deepest_vertical' in keys: dist_diag, dist_vert = self.get_mouse_ground_distance_max(frame_ivals) if 'mouse_deepest_diagonal' in keys: result['mouse_deepest_diagonal'] = dist_diag * self.length_scale if 'mouse_deepest_vertical' in keys: result['mouse_deepest_vertical'] = dist_vert * self.length_scale # get statistics about the burrow evolution if any(key in keys for key in ('burrow_area_excavated', 'mouse_digging_rate', 'time_burrow_grew')): stats = [self.get_burrow_growth_statistics((f.start, f.stop)) for f in frame_slices] stats = np.array(stats) if 'burrow_area_excavated' in keys or 'mouse_digging_rate' in keys: try: area_excavated = stats[:, 0] * self.length_scale**2 except IndexError: area_excavated = [] result['burrow_area_excavated'] = area_excavated if 'time_burrow_grew' in keys: try: burrow_time = stats[:, 1] * self.time_scale except IndexError: burrow_time = [] result['time_burrow_grew'] = burrow_time result['fraction_burrow_grew'] = burrow_time / period_durations # calculate the digging rate by considering burrows and the mouse if 'mouse_digging_rate' in keys: time_min = self.params['mouse/digging_rate_time_min'] if self.use_units: time_min *= self.time_scale unit_rate = self.length_scale**2 / self.time_scale area_min = 0 * self.length_scale**2 else: unit_rate = 1 area_min = 0 # calculate the digging rate digging_rate = [] for area, time in itertools.izip(result['burrow_area_excavated'], result['time_at_burrow_end']): if area > area_min and time > time_min: digging_rate.append(area / time) else: digging_rate.append(np.nan * unit_rate) result['mouse_digging_rate'] = digging_rate # determine the remaining keys if not isinstance(keys, OmniContainer): keys = set(keys) - set(result.keys()) if not isinstance(keys, OmniContainer): keys = set(keys) - set(result.keys()) if keys: # report statistics that could not be calculated self.logger.warn('The following statistics are not defined in ' 'the algorithm and could therefore not be ' 'calculated: %s', ', '.join(keys)) return result