def match_track(gen_track, expected_tracks): score = None match = None MAX_ERROR = 8 for track in expected_tracks: start_diff = abs(track.start - gen_track.start_s) gen_start = gen_track.bounds_history[0] distance = tools.eucl_distance( (track.start_pos.mid_x, track.start_pos.mid_y), (gen_start.mid_x, gen_start.mid_y), ) distance += tools.eucl_distance( (track.start_pos.x, track.start_pos.y), (gen_start.x, gen_start.y) ) distance += tools.eucl_distance( (track.start_pos.right, track.start_pos.bottom), (gen_start.right, gen_start.bottom), ) distance /= 3.0 distance = math.sqrt(distance) # makes it more comparable to start error distance /= 4.0 new_score = distance + start_diff if new_score > MAX_ERROR: continue if score is None or new_score < score: match = track score = new_score return match
def get_track_region_score(self, region: Region, moving_vel_thresh): """ Calculates a score between this track and a region of interest. Regions that are close the the expected location for this track are given high scores, as are regions of a similar size. """ if abs(self.vel_x) + abs(self.vel_y) >= moving_vel_thresh: expected_x = int(self.last_bound.mid_x + self.vel_x) expected_y = int(self.last_bound.mid_y + self.vel_y) distance = eucl_distance((expected_x, expected_y), (region.mid_x, region.mid_y)) else: expected_x = int(self.last_bound.x + self.vel_x) expected_y = int(self.last_bound.y + self.vel_y) distance = eucl_distance((expected_x, expected_y), (region.x, region.y)) distance += eucl_distance( ( expected_x + self.last_bound.width, expected_y + self.last_bound.height, ), (region.x + region.width, region.y + region.height), ) distance /= 2.0 # ratio of 1.0 = 20 points, ratio of 2.0 = 10 points, ratio of 3.0 = 0 points. # area is padded with 50 pixels so small regions don't change too much size_difference = (abs(region.area - self.last_bound.area) / (self.last_bound.area + 50)) * 100 return distance, size_difference
def average_distance(self, other): """Calculates the distance between 2 regions by using the distance between (top, left), mid points and (bottom,right) of each region """ expected_x = int(other.mid_x) expected_y = int(other.mid_y) distance = tools.eucl_distance( (expected_x, expected_y), (self.mid_x, self.mid_y) ) expected_x = int(other.x) expected_y = int(other.y) distance += tools.eucl_distance((expected_x, expected_y), (self.x, self.y)) distance += tools.eucl_distance( ( other.right, other.bottom, ), (self.right, self.bottom), ) distance /= 3.0 return distance
def get_stats(self): """ Returns statistics for this track, including how much it moves, and a score indicating how likely it is that this is a good track. :return: a TrackMovementStatistics record """ if len(self) <= 1: return TrackMovementStatistics() # get movement vectors only from non blank regions non_blank = [bound for bound in self.bounds_history if not bound.blank] mass_history = [int(bound.mass) for bound in non_blank] variance_history = [ bound.pixel_variance for bound in non_blank if bound.pixel_variance ] movement = 0 max_offset = 0 frames_moved = 0 avg_vel = 0 first_point = self.bounds_history[0].mid for i, (vx, vy) in enumerate(zip(self.vel_x, self.vel_y)): region = self.bounds_history[i] if not region.blank: avg_vel += abs(vx) + abs(vy) if i == 0: continue if region.blank or self.bounds_history[i - 1].blank: continue if region.has_moved( self.bounds_history[i - 1]) or region.is_along_border: distance = (vx**2 + vy**2)**0.5 movement += distance offset = eucl_distance(first_point, region.mid) max_offset = max(max_offset, offset) frames_moved += 1 avg_vel = avg_vel / len(mass_history) # the standard deviation is calculated by averaging the per frame variances. # this ends up being slightly different as I'm using /n rather than /(n-1) but that # shouldn't make a big difference as n = width*height*frames which is large. max_offset = math.sqrt(max_offset) delta_std = float(np.mean(variance_history))**0.5 jitter_bigger = 0 jitter_smaller = 0 for i, bound in enumerate(self.bounds_history[1:]): prev_bound = self.bounds_history[i] if prev_bound.is_along_border or bound.is_along_border: continue height_diff = bound.height - prev_bound.height width_diff = prev_bound.width - bound.width thresh_h = max(Track.MIN_JITTER_CHANGE, prev_bound.height * Track.JITTER_THRESHOLD) thresh_v = max(Track.MIN_JITTER_CHANGE, prev_bound.width * Track.JITTER_THRESHOLD) if abs(height_diff) > thresh_h: if height_diff > 0: jitter_bigger += 1 else: jitter_smaller += 1 elif abs(width_diff) > thresh_v: if width_diff > 0: jitter_bigger += 1 else: jitter_smaller += 1 movement_points = (movement**0.5) + max_offset delta_points = delta_std * 25.0 jitter_percent = int( round(100 * (jitter_bigger + jitter_smaller) / float(self.frames))) blank_percent = int(round(100.0 * self.blank_frames / self.frames)) score = (min(movement_points, 100) + min(delta_points, 100) + (100 - jitter_percent) + (100 - blank_percent)) stats = TrackMovementStatistics( movement=float(movement), max_offset=float(max_offset), average_mass=float(np.mean(mass_history)), median_mass=float(np.median(mass_history)), delta_std=float(delta_std), score=float(score), region_jitter=jitter_percent, jitter_bigger=jitter_bigger, jitter_smaller=jitter_smaller, blank_percent=blank_percent, frames_moved=frames_moved, mass_std=float(np.std(mass_history)), average_velocity=float(avg_vel), ) return stats
def movement_images( frames, regions, dim, channel=TrackChannels.filtered, require_movement=False, ): """Return 2 images describing the movement, one has dots representing the centre of mass, the other is a collage of all frames """ i = 0 dots = np.zeros(dim) overlay = np.zeros(dim) prev = None prev_overlay = None line_colour = 60 dot_colour = 120 img = Image.fromarray(np.uint8(dots)) d = ImageDraw.Draw(img) # draw movment lines and draw frame overlay center_distance = 0 min_distance = 2 for i, frame in enumerate(frames): region = regions[i] x = int(region.mid_x) y = int(region.mid_y) # writing dot image if prev is not None: d.line(prev + (x, y), fill=line_colour, width=1) prev = (x, y) # writing overlay image if require_movement and prev_overlay: center_distance = eucl_distance( prev_overlay, ( x, y, ), ) if (prev_overlay is None or center_distance > min_distance) or not require_movement: frame = frame[channel] subimage = region.subimage(overlay) subimage[:, :] += np.float32(frame) center_distance = 0 min_distance = pow(region.width / 2.0, 2) prev_overlay = (x, y) # then draw dots to dot image so they go over the top for i, frame in enumerate(frames): region = regions[i] x = int(region.mid_x) y = int(region.mid_y) d.point([(x, y)], fill=dot_colour) return np.array(img), overlay