exercise.dart 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. import 'package:body_detection/models/pose_landmark_type.dart';
  2. import '../../../navigation/utils/geometry_utils.dart';
  3. import '../widgets/pose_detector.dart';
  4. enum AuthorizedTypeIndex {
  5. nose,
  6. leftShoulder,
  7. rightShoulder,
  8. leftElbow,
  9. rightElbow,
  10. leftWrist,
  11. rightWrist,
  12. leftHip,
  13. rightHip,
  14. leftKnee,
  15. rightKnee,
  16. leftAnkle,
  17. rightAnkle,
  18. }
  19. AuthorizedTypeIndex typeIndexFromString(String typeIndex) {
  20. switch (typeIndex) {
  21. case "nose":
  22. return AuthorizedTypeIndex.nose;
  23. case "leftShoulder":
  24. return AuthorizedTypeIndex.leftShoulder;
  25. case "rightShoulder":
  26. return AuthorizedTypeIndex.rightShoulder;
  27. case "leftElbow":
  28. return AuthorizedTypeIndex.leftElbow;
  29. case "rightElbow":
  30. return AuthorizedTypeIndex.rightElbow;
  31. case "leftWrist":
  32. return AuthorizedTypeIndex.leftWrist;
  33. case "rightWrist":
  34. return AuthorizedTypeIndex.rightWrist;
  35. case "leftHip":
  36. return AuthorizedTypeIndex.leftHip;
  37. case "rightHip":
  38. return AuthorizedTypeIndex.rightHip;
  39. case "leftKnee":
  40. return AuthorizedTypeIndex.leftKnee;
  41. case "rightKnee":
  42. return AuthorizedTypeIndex.rightKnee;
  43. case "leftAnkle":
  44. return AuthorizedTypeIndex.leftAnkle;
  45. case "rightAnkle":
  46. return AuthorizedTypeIndex.rightAnkle;
  47. default:
  48. throw const FormatException("Incorrect type name");
  49. }
  50. }
  51. enum Comparator {
  52. greater,
  53. lesser,
  54. }
  55. Comparator comparatorFromString(String comparator) {
  56. switch (comparator) {
  57. case "greater":
  58. return Comparator.greater;
  59. case "lesser":
  60. return Comparator.lesser;
  61. default:
  62. throw const FormatException("Incorrect comparator name");
  63. }
  64. }
  65. class Exercise {
  66. final int repetitions;
  67. final int sets;
  68. final Criteria? startMovement;
  69. final Criteria? endMovement;
  70. final String description;
  71. final String name;
  72. final int difficulty;
  73. final String id;
  74. late final List<PoseLandmarkType> jointsOnScreen;
  75. Exercise({
  76. required this.repetitions,
  77. required this.sets,
  78. required this.startMovement,
  79. required this.endMovement,
  80. required this.description,
  81. required this.name,
  82. required this.difficulty,
  83. required this.id,
  84. required List<AuthorizedTypeIndex> jointsOnScreen,
  85. }) {
  86. this.jointsOnScreen = jointsOnScreen.map((joint) => authorizedType[joint.index]).toList();
  87. }
  88. bool isAtStartMovement(MeanFilteredData meanFilteredData) {
  89. final start = startMovement;
  90. if (start != null) {
  91. return start.isAtPosition(meanFilteredData);
  92. }
  93. return false;
  94. }
  95. bool isAtEndMovement(MeanFilteredData meanFilteredData) {
  96. final end = endMovement;
  97. if (end != null) {
  98. return end.isAtPosition(meanFilteredData);
  99. }
  100. return false;
  101. }
  102. Map<String, dynamic> toMap() {
  103. return {
  104. "repetitions": repetitions,
  105. "sets": sets,
  106. "start_movement": startMovement?.toMap(),
  107. "end_movement": endMovement?.toMap(),
  108. "description": description,
  109. "name": name,
  110. "difficulty": difficulty,
  111. "joints_on_screen": jointsOnScreen.map((j) => j.name).toList(),
  112. };
  113. }
  114. factory Exercise.fromMap(String id, Map<String, dynamic> map) {
  115. return Exercise(
  116. repetitions: map["repetitions"],
  117. sets: map["sets"],
  118. startMovement: map.containsKey("start_movement") ? Criteria.fromMap(map["start_movement"]) : null,
  119. endMovement: map.containsKey("end_movement") ? Criteria.fromMap(map["end_movement"]) : null,
  120. description: map["description"],
  121. name: map["name"],
  122. difficulty: map["difficulty"],
  123. id: id,
  124. jointsOnScreen: map.containsKey("joints_on_screen") ? List<String>.from(map["joints_on_screen"]).map((e) => typeIndexFromString(e)).toList() : [],
  125. );
  126. }
  127. static const authorizedType = [
  128. PoseLandmarkType.nose,
  129. PoseLandmarkType.leftShoulder,
  130. PoseLandmarkType.rightShoulder,
  131. PoseLandmarkType.leftElbow,
  132. PoseLandmarkType.rightElbow,
  133. PoseLandmarkType.leftWrist,
  134. PoseLandmarkType.rightWrist,
  135. PoseLandmarkType.leftHip,
  136. PoseLandmarkType.rightHip,
  137. PoseLandmarkType.leftKnee,
  138. PoseLandmarkType.rightKnee,
  139. PoseLandmarkType.leftAnkle,
  140. PoseLandmarkType.rightAnkle,
  141. ];
  142. }
  143. abstract class Criteria {
  144. bool isAtPosition(MeanFilteredData meanFilteredData);
  145. Map<String, dynamic> toMap();
  146. static Criteria fromMap(Map<String, dynamic> map) {
  147. switch (map["type"]) {
  148. case "distance":
  149. return CriteriaDistance.fromMap(map);
  150. case "angle":
  151. return CriteriaAngle.fromMap(map);
  152. default:
  153. throw FormatException("Type should be distance or angle but is ${map['type']}");
  154. }
  155. }
  156. }
  157. class CriteriaDistance implements Criteria {
  158. final AuthorizedTypeIndex jointStart;
  159. final AuthorizedTypeIndex jointEnd;
  160. final int axis;
  161. final num threshold;
  162. final Comparator comparator;
  163. const CriteriaDistance({
  164. required this.jointStart,
  165. required this.jointEnd,
  166. required this.axis,
  167. required this.threshold,
  168. required this.comparator,
  169. });
  170. @override
  171. bool isAtPosition(MeanFilteredData meanFilteredData) {
  172. final start = meanFilteredData[jointStart.index][axis];
  173. final end = meanFilteredData[jointEnd.index][axis];
  174. final distance = (start - end).abs();
  175. return _compare(distance, threshold);
  176. }
  177. bool _compare(num distance, num threshold) {
  178. if (comparator == Comparator.greater) {
  179. return distance > threshold;
  180. } else {
  181. return distance < threshold;
  182. }
  183. }
  184. @override
  185. Map<String, dynamic> toMap() {
  186. return {
  187. "joint_start": jointStart.name,
  188. "joint_end": jointEnd.name,
  189. "threshold": threshold,
  190. "comparator": comparator.name,
  191. "type": "distance",
  192. };
  193. }
  194. factory CriteriaDistance.fromMap(Map<String, dynamic> map) {
  195. return CriteriaDistance(
  196. jointStart: typeIndexFromString(map["joint_start"]),
  197. jointEnd: typeIndexFromString(map["joint_end"]),
  198. axis: map["axis"],
  199. threshold: map["threshold"],
  200. comparator: comparatorFromString(map["comparator"]),
  201. );
  202. }
  203. }
  204. class CriteriaAngle implements Criteria {
  205. final AuthorizedTypeIndex jointStart;
  206. final AuthorizedTypeIndex jointCenter;
  207. final AuthorizedTypeIndex jointEnd;
  208. final num threshold;
  209. final Comparator comparator;
  210. const CriteriaAngle({
  211. required this.jointStart,
  212. required this.jointCenter,
  213. required this.jointEnd,
  214. required this.threshold,
  215. required this.comparator,
  216. });
  217. @override
  218. bool isAtPosition(MeanFilteredData meanFilteredData) {
  219. final start = Point3D(
  220. x: meanFilteredData[jointStart.index][0],
  221. y: meanFilteredData[jointStart.index][1],
  222. z: meanFilteredData[jointStart.index][2]);
  223. final center = Point3D(
  224. x: meanFilteredData[jointCenter.index][0],
  225. y: meanFilteredData[jointCenter.index][1],
  226. z: meanFilteredData[jointCenter.index][2]);
  227. final end = Point3D(
  228. x: meanFilteredData[jointEnd.index][0],
  229. y: meanFilteredData[jointEnd.index][1],
  230. z: meanFilteredData[jointEnd.index][2]);
  231. final angle = DistanceUtils.angleBetweenThreePoints(start, center, end).round();
  232. return _compare(angle, threshold);
  233. }
  234. bool _compare(num angle, num threshold) {
  235. if (comparator == Comparator.greater) {
  236. return angle > threshold;
  237. } else {
  238. return angle < threshold;
  239. }
  240. }
  241. @override
  242. Map<String, dynamic> toMap() {
  243. return {
  244. "joint_start": jointStart.name,
  245. "joint_center": jointCenter.name,
  246. "joint_end": jointEnd.name,
  247. "threshold": threshold,
  248. "comparator": comparator.name,
  249. "type": "angle",
  250. };
  251. }
  252. factory CriteriaAngle.fromMap(Map<String, dynamic> map) {
  253. return CriteriaAngle(
  254. jointStart: typeIndexFromString(map["joint_start"]),
  255. jointCenter: typeIndexFromString(map["joint_center"]),
  256. jointEnd: typeIndexFromString(map["joint_end"]),
  257. threshold: map["threshold"],
  258. comparator: comparatorFromString(map["comparator"]),
  259. );
  260. }
  261. }