import 'package:body_detection/models/pose_landmark_type.dart'; import '../../../navigation/utils/geometry_utils.dart'; import '../widgets/pose_detector.dart'; enum AuthorizedTypeIndex { nose, leftShoulder, rightShoulder, leftElbow, rightElbow, leftWrist, rightWrist, leftHip, rightHip, leftKnee, rightKnee, leftAnkle, rightAnkle, } AuthorizedTypeIndex typeIndexFromString(String typeIndex) { switch (typeIndex) { case "nose": return AuthorizedTypeIndex.nose; case "leftShoulder": return AuthorizedTypeIndex.leftShoulder; case "rightShoulder": return AuthorizedTypeIndex.rightShoulder; case "leftElbow": return AuthorizedTypeIndex.leftElbow; case "rightElbow": return AuthorizedTypeIndex.rightElbow; case "leftWrist": return AuthorizedTypeIndex.leftWrist; case "rightWrist": return AuthorizedTypeIndex.rightWrist; case "leftHip": return AuthorizedTypeIndex.leftHip; case "rightHip": return AuthorizedTypeIndex.rightHip; case "leftKnee": return AuthorizedTypeIndex.leftKnee; case "rightKnee": return AuthorizedTypeIndex.rightKnee; case "leftAnkle": return AuthorizedTypeIndex.leftAnkle; case "rightAnkle": return AuthorizedTypeIndex.rightAnkle; default: throw const FormatException("Incorrect type name"); } } enum Comparator { greater, lesser, } Comparator comparatorFromString(String comparator) { switch (comparator) { case "greater": return Comparator.greater; case "lesser": return Comparator.lesser; default: throw const FormatException("Incorrect comparator name"); } } class Exercise { final int repetitions; final int sets; final Criteria? startMovement; final Criteria? endMovement; final String description; final String name; final int difficulty; final String id; late final List jointsOnScreen; Exercise({ required this.repetitions, required this.sets, required this.startMovement, required this.endMovement, required this.description, required this.name, required this.difficulty, required this.id, required List jointsOnScreen, }) { this.jointsOnScreen = jointsOnScreen.map((joint) => authorizedType[joint.index]).toList(); } bool isAtStartMovement(MeanFilteredData meanFilteredData) { final start = startMovement; if (start != null) { return start.isAtPosition(meanFilteredData); } return false; } bool isAtEndMovement(MeanFilteredData meanFilteredData) { final end = endMovement; if (end != null) { return end.isAtPosition(meanFilteredData); } return false; } Map toMap() { return { "repetitions": repetitions, "sets": sets, "start_movement": startMovement?.toMap(), "end_movement": endMovement?.toMap(), "description": description, "name": name, "difficulty": difficulty, "joints_on_screen": jointsOnScreen.map((j) => j.name).toList(), }; } factory Exercise.fromMap(String id, Map map) { return Exercise( repetitions: map["repetitions"], sets: map["sets"], startMovement: map.containsKey("start_movement") ? Criteria.fromMap(map["start_movement"]) : null, endMovement: map.containsKey("end_movement") ? Criteria.fromMap(map["end_movement"]) : null, description: map["description"], name: map["name"], difficulty: map["difficulty"], id: id, jointsOnScreen: map.containsKey("joints_on_screen") ? List.from(map["joints_on_screen"]).map((e) => typeIndexFromString(e)).toList() : [], ); } static const authorizedType = [ PoseLandmarkType.nose, PoseLandmarkType.leftShoulder, PoseLandmarkType.rightShoulder, PoseLandmarkType.leftElbow, PoseLandmarkType.rightElbow, PoseLandmarkType.leftWrist, PoseLandmarkType.rightWrist, PoseLandmarkType.leftHip, PoseLandmarkType.rightHip, PoseLandmarkType.leftKnee, PoseLandmarkType.rightKnee, PoseLandmarkType.leftAnkle, PoseLandmarkType.rightAnkle, ]; } abstract class Criteria { bool isAtPosition(MeanFilteredData meanFilteredData); Map toMap(); static Criteria fromMap(Map map) { switch (map["type"]) { case "distance": return CriteriaDistance.fromMap(map); case "angle": return CriteriaAngle.fromMap(map); default: throw FormatException("Type should be distance or angle but is ${map['type']}"); } } } class CriteriaDistance implements Criteria { final AuthorizedTypeIndex jointStart; final AuthorizedTypeIndex jointEnd; final int axis; final num threshold; final Comparator comparator; const CriteriaDistance({ required this.jointStart, required this.jointEnd, required this.axis, required this.threshold, required this.comparator, }); @override bool isAtPosition(MeanFilteredData meanFilteredData) { final start = meanFilteredData[jointStart.index][axis]; final end = meanFilteredData[jointEnd.index][axis]; final distance = (start - end).abs(); return _compare(distance, threshold); } bool _compare(num distance, num threshold) { if (comparator == Comparator.greater) { return distance > threshold; } else { return distance < threshold; } } @override Map toMap() { return { "joint_start": jointStart.name, "joint_end": jointEnd.name, "threshold": threshold, "comparator": comparator.name, "type": "distance", }; } factory CriteriaDistance.fromMap(Map map) { return CriteriaDistance( jointStart: typeIndexFromString(map["joint_start"]), jointEnd: typeIndexFromString(map["joint_end"]), axis: map["axis"], threshold: map["threshold"], comparator: comparatorFromString(map["comparator"]), ); } } class CriteriaAngle implements Criteria { final AuthorizedTypeIndex jointStart; final AuthorizedTypeIndex jointCenter; final AuthorizedTypeIndex jointEnd; final num threshold; final Comparator comparator; const CriteriaAngle({ required this.jointStart, required this.jointCenter, required this.jointEnd, required this.threshold, required this.comparator, }); @override bool isAtPosition(MeanFilteredData meanFilteredData) { final start = Point3D( x: meanFilteredData[jointStart.index][0], y: meanFilteredData[jointStart.index][1], z: meanFilteredData[jointStart.index][2]); final center = Point3D( x: meanFilteredData[jointCenter.index][0], y: meanFilteredData[jointCenter.index][1], z: meanFilteredData[jointCenter.index][2]); final end = Point3D( x: meanFilteredData[jointEnd.index][0], y: meanFilteredData[jointEnd.index][1], z: meanFilteredData[jointEnd.index][2]); final angle = DistanceUtils.angleBetweenThreePoints(start, center, end).round(); return _compare(angle, threshold); } bool _compare(num angle, num threshold) { if (comparator == Comparator.greater) { return angle > threshold; } else { return angle < threshold; } } @override Map toMap() { return { "joint_start": jointStart.name, "joint_center": jointCenter.name, "joint_end": jointEnd.name, "threshold": threshold, "comparator": comparator.name, "type": "angle", }; } factory CriteriaAngle.fromMap(Map map) { return CriteriaAngle( jointStart: typeIndexFromString(map["joint_start"]), jointCenter: typeIndexFromString(map["joint_center"]), jointEnd: typeIndexFromString(map["joint_end"]), threshold: map["threshold"], comparator: comparatorFromString(map["comparator"]), ); } }