class RepCounter: """ Tracks exercise state transitions over time to count reps and provide dynamic user feedback. Uses asymmetric hysteresis to reject noisy per-frame detections: - entry_frames: consecutive *active* frames needed to confirm entry - exit_frames: consecutive *rest* frames needed to confirm rep completion State names are configurable so the same counter works for any exercise (squats, pull-ups, push-ups, etc.). At 30 fps the defaults (entry=3, exit=15) correspond to ~100 ms and ~500 ms. """ def __init__(self, target_reps: int = 15, rest_state: str = "Standing", active_state: str = "Squatting", entry_frames: int = 3, exit_frames: int = 15): self.target_reps = target_reps self.rest_state = rest_state self.active_state = active_state self._valid_states = {rest_state, active_state} self.entry_frames = entry_frames self.exit_frames = exit_frames self.reps_completed = 0 self.confirmed_state = rest_state self.message = "Let's go!" self._pending_state: str | None = None self._pending_count = 0 def update(self, detected_status: str) -> dict: """ Feed in this frame's raw detector status. Returns {"reps": int, "message": str, "confirmed_state": str}. """ if detected_status not in self._valid_states: return self._result() if detected_status != self.confirmed_state: if detected_status == self._pending_state: self._pending_count += 1 else: self._pending_state = detected_status self._pending_count = 1 threshold = (self.entry_frames if self.confirmed_state == self.rest_state else self.exit_frames) if self._pending_count >= threshold: old = self.confirmed_state self.confirmed_state = detected_status self._pending_state = None self._pending_count = 0 if old == self.active_state and self.confirmed_state == self.rest_state: self.reps_completed += 1 self._update_message() else: self._pending_state = None self._pending_count = 0 return self._result() def _result(self) -> dict: return { "reps": self.reps_completed, "message": self.message, "confirmed_state": self.confirmed_state, } def _update_message(self): if self.reps_completed == self.target_reps: self.message = "SUCCESS!! Target Reached!" elif self.reps_completed > self.target_reps: self.message = "Bonus reps! You're a beast!" elif self.target_reps - self.reps_completed <= 3: self.message = "Almost there! Push!" elif self.reps_completed == self.target_reps // 2: self.message = "Halfway done! Keep it up!" else: self.message = "Good form, keep going."