Skip to content
AF
Open Dataset

Exercise form rules: the joint angles open-source trackers use

Open-source pose-estimation exercise trackers count and check reps with fixed joint-angle thresholds, and they do not agree: one licensed project counts a push-up between elbow angles of 145 and 90 degrees, another between 160 and 90. This dataset collects 22 such thresholds for 7 exercises from 4 licensed projects, each with the line of code it was read from on 2026-11-05, and marks 3 rules null where no source read states a number. They are the authors’ choices, not biomechanical standards. CC BY 4.0, JSON and CSV.

Squat

Load into the rep counter: knee 160°/100° (PythonExcercisePoseEstimation (thphuc05))

RuleMeasuresValueSource and evidence
rep-countBack at the top (standing): knee angle above this, together with the hip angle above its own top value, ends a rep.kneehip · knee · ankle> 160°ExerciseConfig(name="Squat", knee_angle_up=160, knee_angle_down_entry=100,applied: if knee_angle > cfg.knee_angle_up and hip_angle > cfg.hip_angle_up:measured: knee_angle = calculate_angle(hip, knee, ankle)PythonExcercisePoseEstimation (thphuc05), MIT
rep-countGoing down: knee angle below this, from the top, enters the down phase.kneehip · knee · ankle< 100°ExerciseConfig(name="Squat", knee_angle_up=160, knee_angle_down_entry=100,applied: if knee_angle < cfg.knee_angle_down_entry and stage == "up":measured: knee_angle = calculate_angle(hip, knee, ankle)PythonExcercisePoseEstimation (thphuc05), MIT
rep-countBack at the top: hip angle above this is also required to end a rep.hipknee · hip · shoulder> 160°knee_angle_down_entry=100, # Squat hip_angle_up=160, correct_knee_max=95applied: if knee_angle > cfg.knee_angle_up and hip_angle > cfg.hip_angle_up:measured: hip_angle = calculate_angle(knee, hip, shouder)PythonExcercisePoseEstimation (thphuc05), MIT
form-checkDepth: the smallest knee angle of the rep must fall below this (with the hip and hip-drop checks) for the rep to be marked correct.kneehip · knee · ankle< 95°knee_angle_down_entry=100, # Squat hip_angle_up=160, correct_knee_max=95applied: if is_hip_drop and min_knee_angle < cfg.correct_knee_max and min_hip_angle < cfg.correct_hip_max:measured: knee_angle = calculate_angle(hip, knee, ankle)PythonExcercisePoseEstimation (thphuc05), MIT
form-checkDepth: the smallest hip angle of the rep must fall below this (with the knee check) for the rep to be marked correct.hipknee · hip · shoulder< 95°correct_knee_max=95, correct_hip_max=95, hip_drop_min=0.08)applied: if is_hip_drop and min_knee_angle < cfg.correct_knee_max and min_hip_angle < cfg.correct_hip_max:measured: hip_angle = calculate_angle(knee, hip, shouder)PythonExcercisePoseEstimation (thphuc05), MIT
form-checkFoot placement: foot width divided by shoulder width must fall within this range.stance1.2 to 2.8FOOT_SHOULDER_RATIO_THRESHOLDS = [1.2, 2.8]applied: if min_ratio_foot_shoulder <= foot_shoulder_ratio <= max_ratio_foot_shoulder:measured: foot_shoulder_ratio = round(foot_width / shoulder_width, 1)Exercise-Correction (Ngô Hồng Quốc Bảo), MIT

How these are detected on camera: squat detection.

Push-up

Load into the rep counter: elbow 145°/90° (Ultralytics AIGym (workouts monitoring solution)) · elbow 160°/90° (PythonExcercisePoseEstimation (thphuc05))

RuleMeasuresValueSource and evidence
rep-countUp: angle above this sets the stage to up. The documented default keypoints are for push-ups.elbowright_shoulder · right_elbow · right_wristCOCO 6, 8, 10> 145°up_angle: float = 145.0applied: elif state["angle"] > self.up_angle:measured: kpts=[6, 8, 10], # keypoints for monitoring specific exercise, by default it's for pushupUltralytics AIGym (workouts monitoring solution), AGPL-3.0
rep-countDown: angle below this sets the stage to down, and counts a rep if the stage was up (the source counts on reaching the bottom, not on the return).elbowright_shoulder · right_elbow · right_wristCOCO 6, 8, 10< 90°down_angle: float = 90.0applied: if state["angle"] < self.down_angle: if state["stage"] == "up": state["count"] += 1measured: kpts=[6, 8, 10], # keypoints for monitoring specific exercise, by default it's for pushupUltralytics AIGym (workouts monitoring solution), AGPL-3.0
rep-countUp: elbow angle above this, with the body straight, ends a rep.elbowwrist · elbow · shoulder> 160°ExerciseConfig(name="PushUp", elbow_angle_up=160, elbow_angle_down_entry=90,applied: if elbow_angle > cfg.elbow_angle_up:measured: elbow_angle = calculate_angle(wrist, elbow, shouder)PythonExcercisePoseEstimation (thphuc05), MIT
rep-countDown: elbow angle below this, from the top, enters the down phase.elbowwrist · elbow · shoulder< 90°ExerciseConfig(name="PushUp", elbow_angle_up=160, elbow_angle_down_entry=90,applied: if elbow_angle < cfg.elbow_angle_down_entry and stage == "up":measured: elbow_angle = calculate_angle(wrist, elbow, shouder)PythonExcercisePoseEstimation (thphuc05), MIT
form-checkDepth: the smallest elbow angle of the rep must fall below this for the rep to be marked correct.elbowwrist · elbow · shoulder< 90°correct_elbow_max=90, body_straight_min=160)applied: if min_elbow_angle < cfg.correct_elbow_max:measured: elbow_angle = calculate_angle(wrist, elbow, shouder)PythonExcercisePoseEstimation (thphuc05), MIT
form-checkStraight body: the shoulder-hip-ankle angle must stay above this for the source to track the rep at all.body-lineshoulder · hip · ankle> 160°correct_elbow_max=90, body_straight_min=160)applied: is_body_straight = body_line_angle > cfg.body_straight_minmeasured: body_line_angle = calculate_angle(shouder, hip, ankle)PythonExcercisePoseEstimation (thphuc05), MIT

How these are detected on camera: push-up detection.

Lunge

No source read gives both a top and a bottom angle for counting, so there is no rep-counter preset for this exercise.

RuleMeasuresValueSource and evidence
form-checkAt the bottom (the source's down stage): each knee angle must fall within this range, or the rep is flagged with a knee-angle error.kneehip · knee · ankle60° to 125°KNEE_ANGLE_THRESHOLD = [60, 125]applied: if angle_thresholds[0] <= results["right"]["angle"] <= angle_thresholds[1]:measured: results["right"]["angle"] = calculate_angle(right_hip, right_knee, right_ankle)Exercise-Correction (Ngô Hồng Quốc Bảo), MIT
rep-countTop and bottom knee angles for counting lunge reps.kneehip · knee · anklenullThe one licensed lunge source read counts lunge stages with a trained classifier, not an angle threshold, so it states no top or bottom angle. The peer-reviewed state-machine definition cited on /motion/lunge-detection-pose-estimation is on a host the build environment could not reach on the fetch date.STAGE_ML_MODEL_PATH = get_static_file_url("model/lunge_stage_model.pkl") NgoQuocBao1010/Exercise-Correction

How these are detected on camera: lunge detection.

Plank

No source read gives both a top and a bottom angle for counting, so there is no rep-counter preset for this exercise.

RuleMeasuresValueSource and evidence
holdHold: the hip angle must stay above this; dropping below stops the hold timer.hipknee · hip · shoulder> 160°ExerciseConfig(name="Plank", plank_hip_min=160, hold_frames_required=60)applied: if hip_angle > cfg.plank_hip_min:measured: hip_angle = calculate_angle(knee, hip, shouder)PythonExcercisePoseEstimation (thphuc05), MIT
holdHold start: the hip rule must hold for this many consecutive frames before the timer starts (the source's comment says 2 s at 30 fps).—>= 60 framesExerciseConfig(name="Plank", plank_hip_min=160, hold_frames_required=60)applied: if down_frame_counter >= cfg.hold_frames_required and not timer_running:measured: down_frame_counter += 1PythonExcercisePoseEstimation (thphuc05), MIT

How these are detected on camera: plank detection.

Bicep curl

Load into the rep counter: elbow 120°/100° (Exercise-Correction (Ngô Hồng Quốc Bảo))

RuleMeasuresValueSource and evidence
rep-countArm extended (the source's down stage): elbow angle above this.elbowshoulder · elbow · wrist> 120°STAGE_DOWN_THRESHOLD = 120applied: if bicep_curl_angle > self.stage_down_threshold:measured: bicep_curl_angle = int(calculate_angle(self.shoulder, self.elbow, self.wrist))Exercise-Correction (Ngô Hồng Quốc Bảo), MIT
rep-countCurled (the source's up stage): elbow angle below this, after the arm was extended, counts a rep (the source counts on reaching the curl, not on the return).elbowshoulder · elbow · wrist< 100°STAGE_UP_THRESHOLD = 100applied: elif bicep_curl_angle < self.stage_up_threshold and self.stage == "down":measured: bicep_curl_angle = int(calculate_angle(self.shoulder, self.elbow, self.wrist))Exercise-Correction (Ngô Hồng Quốc Bảo), MIT
form-checkFull range: a rep whose smallest elbow angle is this or more is flagged as a peak-contraction error.elbowshoulder · elbow · wrist< 60°PEAK_CONTRACTION_THRESHOLD = 60applied: and self.peak_contraction_angle >= self.peak_contraction_thresholdmeasured: bicep_curl_angle = int(calculate_angle(self.shoulder, self.elbow, self.wrist))Exercise-Correction (Ngô Hồng Quốc Bảo), MIT
form-checkLoose upper arm: an angle between the upper arm and the vertical above this is flagged as an error.upper-arm-verticalelbow · shoulder · vertical<= 40°LOOSE_UPPER_ARM_ANGLE_THRESHOLD = 40applied: if ground_upper_arm_angle > self.loose_upper_arm_angle_threshold:measured: calculate_angle(self.elbow, self.shoulder, shoulder_projection)Exercise-Correction (Ngô Hồng Quốc Bảo), MIT
form-checkFull range: if the smallest elbow angle over the set is above this, the set is flagged as not curled all the way up.elbowshoulder · elbow · wrist<= 70°if upper_arm_forearm_min > 70.0:applied: You are not curling the weight all the way to the top, up to your shoulders.measured: upper_arm_forearm_angles = np.degrees(np.arccos(np.clip(np.sum(np.multiply(upper_arm_vecs, forearm_vecs), axis=1), -1.0, 1.0)))Pose Trainer (Steven Chen and Richard Yang), GPL-3.0
form-checkUpper arm still: if the upper-arm-to-torso angle varies by more than this over the set, the set is flagged for shoulder rotation.upper-arm-torsoshoulder · elbow · hip · neck<= 35°if upper_arm_torso_range > 35.0:applied: Your upper arm shows significant rotation around the shoulder when curling.measured: upper_arm_torso_range = np.max(upper_arm_torso_angles) - np.min(upper_arm_torso_angles)Pose Trainer (Steven Chen and Richard Yang), GPL-3.0

How these are detected on camera: bicep curl detection.

Shoulder press

No source read gives both a top and a bottom angle for counting, so there is no rep-counter preset for this exercise.

RuleMeasuresValueSource and evidence
form-checkLockout: if the largest elbow angle over the set is below this, the set is flagged as not pressed all the way up.elbowshoulder · elbow · wrist>= 178°if upper_forearm_angle < 178:applied: You are not lifting the weight all the way up. Extend your arms through the full range of motion.measured: print("Max upper arm and forearm angle: ", np.max(upper_arm_forearm_angles))Pose Trainer (Steven Chen and Richard Yang), GPL-3.0
rep-countTop and bottom elbow angles for counting shoulder-press reps.elbowshoulder · elbow · wristnullThe one licensed shoulder-press source read judges a whole recorded set by its largest elbow angle and counts no reps, so it states no bottom position and no per-rep top. No other licensed source read gives one.print("Max upper arm and forearm angle: ", np.max(upper_arm_forearm_angles)) stevenzchen/pose-trainer

How these are detected on camera: shoulder press detection.

Deadlift

No source read gives both a top and a bottom angle for counting, so there is no rep-counter preset for this exercise.

RuleMeasuresValueSource and evidence
rep-countTop and bottom hip angles for counting deadlift reps.hipshoulder · hip · kneenullNo licensed open-source deadlift rule could be read: the two deadlift repositories found ship no LICENSE file. The peer-reviewed state-machine definition cited on /motion/deadlift-pose-estimation is on a host the build environment could not reach on the fetch date, so its thresholds are not copied; it is listed in ops/sources/form-rules.txt for a snapshot run.

How these are detected on camera: deadlift detection.

Sources

  • Ultralytics AIGym (workouts monitoring solution) (ultralytics/ultralytics, branch main), licensed AGPL-3.0, built on Ultralytics YOLO pose (COCO 17 keypoints). Angles computed as radians = math.atan2(c[1] - b[1], c[0] - b[0]) - math.atan2(a[1] - b[1], a[0] - b[0]). Files read on 2026-11-05: ultralytics/solutions/config.py, ultralytics/solutions/ai_gym.py, ultralytics/solutions/solutions.py, docs/en/guides/workouts-monitoring.md.
  • Pose Trainer (Steven Chen and Richard Yang) (stevenzchen/pose-trainer, branch master), licensed GPL-3.0, built on OpenPose. Angles computed as upper_arm_forearm_angles = np.degrees(np.arccos(np.clip(np.sum(np.multiply(upper_arm_vecs, forearm_vecs), axis=1), -1.0, 1.0))). Its README warns: WARNING: This code is experimental, and is no longer maintained or supported. It is not expected to work. Files read on 2026-11-05: evaluate.py, README.md.
  • Exercise-Correction (Ngô Hồng Quốc Bảo) (NgoQuocBao1010/Exercise-Correction, branch main), licensed MIT, built on MediaPipe Pose (BlazePose 33 landmarks). Angles computed as angleInDeg = angleInDeg if angleInDeg <= 180 else 360 - angleInDeg. Files read on 2026-11-05: web/server/detection/bicep_curl.py, web/server/detection/lunge.py, web/server/detection/squat.py, web/server/detection/plank.py, web/server/detection/utils.py.
  • PythonExcercisePoseEstimation (thphuc05) (thphuc05/PythonExcercisePoseEstimation, branch main), licensed MIT, built on MediaPipe Pose Landmarker (BlazePose 33 landmarks). Angles computed as if angle > 180.0: angle = 360 - angle. Files read on 2026-11-05: pose_estimation.py.

How this dataset is built, and its limits

  • Only code under an open-source license whose LICENSE file could be read is used. Repositories whose README claims a license but that ship no LICENSE file were left out.
  • No number is typed by hand. The generator fetches each file, refuses to write unless the quoted line is in it, and captures the value from that line; the line that applies the value and the line that computes the angle are stored and checked the same way, and a weekly job re-checks every quote against the live file.
  • Peer-reviewed definitions, such as the finite-state exercise definitions cited on deadlift detection, were on hosts the build environment could not reach on 2026-11-05. Their numbers are not copied until a fetched copy can be quoted, which is why the deadlift rules are null.
  • Every source measures 2D angles in the image, from one model and one camera setup its author chose. A threshold that suits a side-on phone at hip height can fail from the front. The values are starting points to test, not a standard of correct form, and nothing here is coaching or medical advice.
  • Some sources count a rep on the way down, others on the way back up; the “meaning” column says which, in the source’s own terms where they differ from the rep counter’s.

An open dataset from AIFitnessAPI, CC BY 4.0, listed with the others on open datasets. Test a rule with the rep counter playground; the method behind it is how form feedback works.