class Simulator(object): def __init__(self, params, scene, initial_pos): self.scene = scene self.params = params self.initial_pos = initial_pos self.status = "none" self.camera = CameraModel(scene, initial_pos, simulate_backlash=self.params.backlash, simulate_noise=self.params.noise) if params.perfect_classification is None: self.perfect_classification = None else: self.perfect_classification = \ params.perfect_classification[scene.filename] def _do_local_search(self, direction, rev_direction): """Perform a local search (incremental hillclimbing in a given direction). The hillclimbing has a tolerance of two steps. i.e., Up to two steps that don't increase the focus value can be taken before we stop climbing.""" while not self.camera.will_hit_edge(direction): prev_fmeasure = self.camera.last_fmeasure() self.camera.move_fine(direction) if self.camera.last_fmeasure() < prev_fmeasure: if (two_step_tolerance and not self.camera.will_hit_edge(direction)): # We've seen a decrease. Consider moving one extra step. prev_fmeasure = self.camera.last_fmeasure() self.camera.move_fine(direction) if (self.camera.last_fmeasure() < prev_fmeasure): # Seen a decrease again, backtrack and stop. self.camera.move_fine(rev_direction, 2) break else: # Backtrack and stop. self.camera.move_fine(rev_direction) break def _go_to_max(self): """Return to the location of the largest focus value seen so far and perform a local search to find the exact location of the peak.""" current_pos = self.camera.last_position() maximum_pos = max(self.camera.visited_positions, key=(lambda pos : self.camera.get_fvalue(pos))) if maximum_pos < current_pos: direction = Direction("left") elif maximum_pos > current_pos: direction = Direction("right") elif current_pos < self.camera.visited_positions[-2]: direction = Direction("left") else: direction = Direction("right") rev_direction = direction.reverse() # Take as many coarse steps as needed to go back to the maximum # without going over it. distance = abs(current_pos - maximum_pos) coarse_steps = distance / 8 self.camera.move_coarse(direction, coarse_steps) # Keep going in fine steps to see if we can find a higher position. start_pos = self.camera.last_position() self._do_local_search(direction, rev_direction) # If we didn't move further, we might want to look in the other # direction too. if start_pos == self.camera.last_position(): self._do_local_search(rev_direction, direction) self.status = "foundmax" def _get_first_direction(self): """Direction in which we should start sweeping initially.""" first, second, third = self.camera.get_fvalues( self.camera.visited_positions[-3:]) norm_lens_pos = float(self.initial_pos) / (self.scene.step_count - 1) evaluator = featuresfirststep.firststep_feature_evaluator( first, second, third, norm_lens_pos) return Direction(evaluatetree.evaluate_tree( self.params.left_right_tree, evaluator)) def _sweep(self, direction): """Sweep the lens in one direction and return a tuple (success state, number of steps taken) along the way. """ initial_position = self.camera.last_position() sweep_fvalues = [ self.camera.last_fmeasure() ] while not self.camera.will_hit_edge(direction): # Move the lens forward. self.camera.move_coarse(direction) sweep_fvalues.append(self.camera.last_fmeasure()) # Take at least two steps before we allow turning back. if len(sweep_fvalues) < 3: continue if self.perfect_classification is None: # Obtain the ML classification at the new lens position. evaluator = featuresturn.action_feature_evaluator( sweep_fvalues, self.scene.step_count) classification = evaluatetree.evaluate_tree( self.params.action_tree, evaluator) else: key = featuresturn.make_key(str(direction), initial_position, self.camera.last_position()) classification = self.perfect_classification[key] if classification != "continue": assert (classification == "turn_peak" or classification == "backtrack") return classification, len(sweep_fvalues) - 1 # We've reached an edge, but the decision tree still does not want # to turn back, so what do we do now? # After thinking a lot about it, I think the best thing to do is to # introduce a condition manually. It's a bit ad-hoc, but we really need # to be able to handle this case robustly, as there are lot of cases # (i.e., landscape shots) where peaks will be at the edge. min_val = min(self.camera.get_fvalues(self.camera.visited_positions)) max_val = max(self.camera.get_fvalues(self.camera.visited_positions)) if float(min_val) / max_val > 0.8: return "backtrack", len(sweep_fvalues) - 1 else: return "turn_peak", len(sweep_fvalues) - 1 def _backtrack(self, previous_direction, step_count): """From the current lens position, go back to the lens position we were at before and look on the other side.""" new_direction = previous_direction.reverse() # Go back to where we started. self.camera.move_coarse(new_direction, step_count) # Sweep again the other way. result, step_count = self._sweep(new_direction) if result == "turn_peak": self._go_to_max() elif result == "backtrack": # If we need to backtrack a second time, we failed. self.status = "failed" else: assert False def evaluate(self): """For every scene and every lens position, run a simulation and store the statistics.""" # Take the first two steps, as to get three focus measures with which # to decide which direction to sweep. self.camera.move_fine(Direction("right"), 2) # Decide initial direction in which to look. direction = self._get_first_direction() # Search in that direction. result, step_count = self._sweep(direction) if result == "turn_peak": self._go_to_max() elif result == "backtrack": self._backtrack(direction, step_count) else: assert False def is_true_positive(self): """Whether a peak was found and the peak is close to a real peak.""" return (self.status == "foundmax" and self.scene.distance_to_closest_peak( self.camera.last_position()) <= 1) def is_false_positive(self): """Whether a peak was found and the peak not close to a real peak.""" return (self.status == "foundmax" and self.scene.distance_to_closest_peak( self.camera.last_position()) > 1) def is_true_negative(self): """Whether we failed to find a peak and we didn't come close to a real peak.""" return (self.status == "failed" and all(self.scene.distance_to_closest_peak(pos) > 1 for pos in self.camera.visited_positions)) def is_false_negative(self): """Whether we failed to find a peak but we did come close to a real peak.""" return (self.status == "failed" and any(self.scene.distance_to_closest_peak(pos) <= 1 for pos in self.camera.visited_positions)) def get_evaluation(self): """Return whether a simulation for this scene starting at the given lens position gave a true/false positive/negative. """ if self.is_true_positive(): return "true positive" if self.is_false_positive(): return "false positive" if self.is_true_negative(): return "true negative" if self.is_false_negative(): return "false negative"