From 41c896ed7d622c855c593c8531652f660bc5bee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E5=AD=90=E6=82=A6?= <1731695011@qq.com> Date: Thu, 5 Jun 2025 13:41:57 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9C=80=E7=BB=88=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/.gradle/8.5/checksums/checksums.lock | Bin 17 -> 17 bytes .../.gradle/8.5/checksums/md5-checksums.bin | Bin 28747 -> 29247 bytes .../.gradle/8.5/checksums/sha1-checksums.bin | Bin 42311 -> 43445 bytes .../8.5/executionHistory/executionHistory.bin | Bin 2772268 -> 3040955 bytes .../executionHistory/executionHistory.lock | Bin 17 -> 17 bytes android/.gradle/8.5/fileHashes/fileHashes.bin | Bin 135283 -> 147883 bytes .../.gradle/8.5/fileHashes/fileHashes.lock | Bin 17 -> 17 bytes .../8.5/fileHashes/resourceHashesCache.bin | Bin 30737 -> 70621 bytes .../buildOutputCleanup.lock | Bin 17 -> 17 bytes .../buildOutputCleanup/outputFiles.bin | Bin 327041 -> 333791 bytes android/.gradle/config.properties | 2 + android/.gradle/file-system.probe | Bin 8 -> 8 bytes android/app/build.gradle | 4 + android/app/src/main/AndroidManifest.xml | 13 + .../poseestimation/AgeSelectionActivity.kt | 3 + .../examples/poseestimation/DataFragment.kt | 62 +++- .../poseestimation/EditProfileActivity.kt | 139 ++++++++- .../poseestimation/ExerciseDetailActivity.kt | 2 +- .../poseestimation/GenderSelectionActivity.kt | 5 +- .../poseestimation/HeightSelectionActivity.kt | 19 +- .../poseestimation/MainTabActivity.kt | 18 +- .../poseestimation/Onboarding3Fragment.kt | 3 + .../poseestimation/OnboardingActivity.kt | 4 +- .../poseestimation/OnboardingAdapter.kt | 9 +- .../poseestimation/SettingFragment.kt | 63 +++- .../examples/poseestimation/SignupActivity.kt | 5 +- .../poseestimation/VideoAnalysisActivity.kt | 286 ++++++++++++++++++ .../poseestimation/VideoAnalysisAdapter.kt | 71 +++++ .../VideoAnalysisResultActivity.kt | 30 ++ .../poseestimation/WeightSelectionActivity.kt | 4 + .../poseestimation/data/AppDatabase.kt | 21 +- .../poseestimation/data/UserProfile.kt | 3 +- .../poseestimation/data/UserProfileDao.kt | 6 +- .../data/VideoAnalysisResult.kt | 15 + .../data/VideoAnalysisResultDao.kt | 19 ++ .../evaluator/DeadliftEvaluator.kt | 239 +++++++++++++++ .../evaluator/ExerciseEvaluator.kt | 9 + .../evaluator/PlankEvaluator.kt | 198 ++++++++++++ .../evaluator/PullUpEvaluator.kt | 240 +++++++++++++++ .../evaluator/PushUpEvaluator.kt | 237 +++++++++++++++ .../evaluator/SquatEvaluator.kt | 218 +++++++++++++ .../main/res/layout/activity_edit_profile.xml | 107 +++++++ .../src/main/res/layout/activity_main_tab.xml | 29 +- .../res/layout/activity_video_analysis.xml | 58 ++++ .../layout/activity_video_analysis_result.xml | 67 ++++ .../app/src/main/res/layout/fragment_data.xml | 28 +- .../src/main/res/layout/fragment_setting.xml | 23 ++ .../main/res/layout/item_exercise_card.xml | 7 +- .../res/layout/item_video_analysis_card.xml | 71 +++++ android/app/src/main/res/xml/file_paths.xml | 4 + 50 files changed, 2292 insertions(+), 49 deletions(-) create mode 100644 android/.gradle/config.properties create mode 100644 android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/VideoAnalysisActivity.kt create mode 100644 android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/VideoAnalysisAdapter.kt create mode 100644 android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/VideoAnalysisResultActivity.kt create mode 100644 android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/data/VideoAnalysisResult.kt create mode 100644 android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/data/VideoAnalysisResultDao.kt create mode 100644 android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/evaluator/DeadliftEvaluator.kt create mode 100644 android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/evaluator/ExerciseEvaluator.kt create mode 100644 android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/evaluator/PlankEvaluator.kt create mode 100644 android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/evaluator/PullUpEvaluator.kt create mode 100644 android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/evaluator/PushUpEvaluator.kt create mode 100644 android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/evaluator/SquatEvaluator.kt create mode 100644 android/app/src/main/res/layout/activity_video_analysis.xml create mode 100644 android/app/src/main/res/layout/activity_video_analysis_result.xml create mode 100644 android/app/src/main/res/layout/item_video_analysis_card.xml create mode 100644 android/app/src/main/res/xml/file_paths.xml diff --git a/android/.gradle/8.5/checksums/checksums.lock b/android/.gradle/8.5/checksums/checksums.lock index 4ef611b83edc482c96283b5ff07002a013c5e6e0..ae37106252d4b09305f1df61ac3f8a64d0fb0304 100644 GIT binary patch literal 17 VcmZQR;AJYp_t5(b0~j!!1OO{01W*6~ literal 17 VcmZQR;AJYp_t5(b0~j#P0{|-f1SJ3f diff --git a/android/.gradle/8.5/checksums/md5-checksums.bin b/android/.gradle/8.5/checksums/md5-checksums.bin index 60545700bd1c741edc66fb0c2f7703c7c3a16779..36f1fc6c155635568f77e2a7c751f86eadb976c4 100644 GIT binary patch delta 755 zcmX^8fN}p5#tkMCjMo>NN-zs7y-}d97q`KP0St7PW!>z}+?m-nl+?=bt zfmy(zap$(mbN@P_N?kXLs()Y-NSax$XFl~N6IAW`$$vFx2xL8J%3XK6@+MTN@Z?^t z42}F}d#~-0eUc6pTnTeRNr=6a*WyXWzW2 znn0`w!U9$GtgW>?Iu2`>N%bu)ay`$$z&PFd7`GV22+j{cS`Sr6D(8zBEj%ElK)`AbB+xA3YzkXn`Dgpd zs7seM9vH2C0o7pz*1*7)f~jK{vryMm79F|3NwIZiuO+;oIuao|SleJa>Jfg|{J3e! z=fW*<`<3(>;s0<|O@rUjv+VbQUd6U9w_w0-;PcW%n&l@J|#E1){^F?DQX z`1W?*)X++ni0xH}n2NVBl!F5J^ap4%Ve`h+a!9LT`I0F~AOB2#Hm_h!j||w7(;ucl kEMaZM)S>tI!|goj7_&@w)(Pgq0mmRZJ~=^kRDpB=0BhpX-2eap delta 68 zcmV-K0K5OcE_L>@}5lmCX?#F-B|tvBDiAt zWLL!ofdjvv%>84Q>;(~A_I>hR#Tf!eKULI3JE>+swbxGeRhr3PC7ZI$y?QNFxAo?` zN(Y(vyZFrK*R6g4)!n^$s_ITge)m2Lnpu0Jt4qX`m!SCl0*(vP=4}S zy^Z{lmAy?SM@~Y-R@~hjs&C20@2gt*-GWsEDp)wV)%1seb@!~dAG?A#Lj+d}PcAjH z5U{B^{i>RCw-H2e`OL{n%^C#kFTVWK_*>7^hnw;}(OM!TDiwVU=+G)g@CmvhQ`0U|BTnB)f^FgAV63sJiPA zb*%b88qIW*X631SX8Au__4m3N>)oHX=C|loL3HteR09F4J&+(m7tre2oi@@qme=viX&gPA2`ikQ*U3X7f zYdLH!e;LkpEB8-UG%%QeY4-GoDG*((t(dxU4<+mF-nQK4KKHJ*w;BtRk8W!_4@v5$ zKRH3Q0dov`4q^5#x|(PDdegt&X#rbxmacyLc$p>GvNK^e5M9y{n7X!_?*Gv1ZU0xv z%;nZ~jj;RY_scm#b%A0O2vpw!33Ifhe$;sDhL-uo+Q;4&GOX!NDQG_Y6lz%nBww-0 JWdpSt0RUa}$B_U4 delta 123 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%0AdQv*S@3jz7wM))wC^$UR zz1oZIA1Qc&Yy&kaddf0=T%y_)B#I;|bjZlwe8I|G(_hm(E@OYB==2N{K(vf^K5aXl zFH5~gjx|jRyTp8bGPS;w{Xq*GgArMzJ&QD4TK6x{v3ds}0nYf5ri)Jdk-ig1d#_i& z1!n{#)1m(;CUF|GFh~@VEJU+~Bq~BW0EI;%0VKO&Mpj%23C@gvJROrz5Y5xlvqVH0 zN72(H#2#d&%Zva+o)A4n>n1J$!3qW7Bhv;v$b z)GTZ$nm(l1yIoYfTE?9Sve?QaaQpo`E7SU{jBU+403F|mF1U||_2GZpwj!c)b85$z zv_?L4yYZyD;uUeed{^1i&8W>ISh%GC7Q z(63Ed@`8=-F@|Si>w}(CUx5SadU2@t%%{nB*FN496DrXld)Vf3&UcbFau?_}qE1c& zHq-iclZa??=rMM&@%zK0w?EtakYQ4RY!`1NS^QxmN#r1@H46bH(t0CC!0}r2Pn$$^ zl=J@e4=l91m+oqb7aq=yMY0ETAqN5d6_HCvch#I%M0>t_w^d!-bG~@`8_~G?s5_!M z+9buWHA!dMo09OC`-D%buBKoqg@8L{m>n%==SFhpJ&%t1x zwy{1M?V-ne#V`?#P*~S|Pg9gE545qt=nwUQ(QpoD zDOZ9A5gn}r6;XySNeM~X@;5t|5`!MZ^`Bqf?PQ-pk!^n=_FHU742q4`^AF&In4*9b z{FDY)@1lyp_BraOF)%DH;{C0aM%D|#ffuNw>c9vQkGatTpf6B$|ACPr7XM5^^8euV zpIeU-gMvjYcTf>d2y%Dnt4N)exeioeb2r<71)6vyL(mGKp|7vX6s}4fye|>CsvQuG{;A88#uv%)*jj6k)@% z*FM?WXjI64;xu5EG0+^*CmLD8sJa(&zVvgo+Xg=z?)DliyU+EP!R;G3CNojMML%AZ>XqfsQ!lg zV7a!Gi5Ab_-_MfG7O*(}d=8su<>$xd`C0OKRzgdrfCo|n>8fPBiolFx&H^d3bX9=f zEb0HMT#KB6271wkgC?EzPW|i~#7CYNJ8s8PXV0VUK=s0WwtNP+*V>@i~Nl z)qw8Lj>ySax?vHZ-9=sbW<>|N8eGjua4M8BRC($l9G)m z?wfAB;{Ts9{)02dm@JuX2yz5BuN0O6kY#XRlr+k6@XYX!^9Lv=kn9J|8^B?bJQFa9 zq}^JyVP&NFuqCRhk^J^h|J&$+p4~8+tTlvB3-M4i@TeugWA|*C;W`5u+wWt`$X>>$ zX_tUbmztsI$wrx5bM^nJJO{3Uu4mUe>i}jRSz3gAmLW?GT9AuZ@D@L0LI#ue{E#VD z9k}HGfzNB*8X$59RT(7o$S^#_s4YY+*A7_NW7$9kv6PTN_Q78X_R9hHMmodf;!Agv8A1Jj>YV_&83abtqh^#|bPskZc@qd83?`%JPIqW_PzTM`ntK=DDcF>qc_wiRitC&weB_HC_!J(-8~grE$2BDDWFFf}fXBaR1zi^`*+gbgkKyPY zX;(-{9;mILtAoQ3O*)EcxVDLGN;-@|-J8f`^!_m>6#;Bs6GS{ANnvqmEjqlJycZc1 ze1G+#xHO+E8WfY+sn|-aFDB;~kQp9gQ~?%~1K&(BtC51B)g<@B-wy1T_m5uC5P4jp zWfDZQipf&Me|+s&1TrmQ#m>I%WG7it9t<1s<2ad3YQxNAT1K8u+KS=UoglMFA2HmC zGV&A>7G8i>4*8a0(4>=O4^jmN6`h2*^%!)soILFx8}|W~j>;yYu!H1ung4L(W7)DK zO9{f;6bZ!TwFELo>Yt}}d(f7}m??Uf#kfL1TtMYbhM5+N?++{a__k~Tm+!|HvaMLQ z{vv7HB ze;WbEk0XFZg#Jv9kjeGuTMBKh1%3iQKU+(dKj7su)KTxM1y7b5*qpo9Vy)tv$@9iU z%|xa<8INVPZTM`i&`)U1=X1DBwzWTt#p3X7tXM)8)1PZ&DK^2E@oqNeexwOGLL<*- z?=-&SZEFcR&}MvC3Tb1-VOjZETMC&>YXQrT2^Deqpr``&nx+^&k~V(pk|D?E&YeSB zYYJKpGG6~n>qj1Ds1CM10^r*Vw7&AcFg&hdkl`^602-bvI(m{}^KY!LMjvc`Ov}G9 zzbbvO{aMFg1VQ^-i-uif#Q(Ds@No=wLJ7lk$Upi3_I;2Oa2poXGMo_T3SceRGVx7X)mGz>hPMY7i6U<^WGR52MljK~WMgqo#iX}(7ONJ83u?G-{ng0m zQ*RKn<9^d7y!=6`3Q;}-I-W92eXKc_@Nwe#Tl)EPc(ztHe7=Ck725LIunL&N;`XnF z?O*-KGViDVzR8li-C-N9m8oT>cU~Fm55{!T&4I;yhLcG5C8IzNq)wolX#3gtS@UfD zY;Cx(DAk|Kfq4X$-P*#ESU!{I#|AASWECTRV03UuR4nY)d_nIx#D>~6e2l@lo=dMb z>%Y46QqrgfG-nY^wOLFSOTcEbm^L z#JXZDPoCIIb9=g9&N7}^^R5}pv!-*i!{^Wd;WHx=nS=YBjt<=B#A{MTVG|iA$p7B& z27mH9nxEOYqN5>x8t)7{z=J*B0qDk|reIeaeX_`xs4!0fV1=aGHvhu!Mt|vdbmyHz zSHDg0u?$n%x^I=~G*D|wHJ$mN@;W;5z1P%w{fZd@uS@5tFQ^Iw4xMx}5p?<`dC;;< z?%(-)_+R)NRUKjh=ExKGEqqZ|7Z-c#6llqU58A(Wx5h7bqdrT?BPLFsdg9id#;&B3 z-HQRwnr;oftpyI)qH{z#bL42_ka57_yj(2M9wWy9UxF1*ppM-s?Rob?zHTfoSv=V# z>N`l@DyIsvHY&u97R2#FVtFy)Q5Np8(eu3`rpEAM1TeDVctrF-5%Z1*2#TZ zVlxWK8ps18%M@bA{n9Zt66$j5Q`xIVc|pgTri>!V!?qWF!=Em8MmGF`4OD6|Y(ULc zxlt2;sdI`KMEM6s3kGV`%{~;iV@kGn`Uvd^@}4s!1pJxvm(t(**sClsJZ!QcI+_>I zH(I{GjiT?RgKfe4?zX0W5`KnC#RK`t3Cd2$EgJu;dtz%0?A#*nUAuN&8GS=$@i%3? z(2Z?K&Y$=OTFyO$7ZyBFRo<-b)F0%jS6<}Jx+IXaIE8HZ1KVIfhJ^I@2BnuSUv<$&GVz}LVi~9b zckwSW5p68k!?)Pncys>i3zZ8_CrBfi16Y1J%js7~#=-m@5-1!LQ}3JTMz)O9v^L&q zwM%CA8W1pu6O^BjOK|w5N<8L)CboCch#GgjMW4?!p063Sd@|-O%r-yyfJc}@!l++r z`r7$@Y@Y&IG6CN|wjWF{UOl;EW+{^0j|+4hm0JdMOlcG#2vz6+c1Pu69j0S-q7J=f zq7Hl>yowUl`@oF#W$mp+ca8l%w(3mFjm}hL^PiZpj%{RAX+zAHVe8M8cad^qT$>!3k#J5pM z4$xh$peUMmT#kdF+3jlc=0ABH9qgg8xGTZPNmQJs5RYI_$a+H}>gE}`J!eIgipPmr zlGRh3Balt~tl^2(!GfA2a*IIn137t6bV_a;7_&-YH@arV(xt1yXI*a?-Fluam=rDg zep=22LG7H4kL*p#xiZbfZ1wr05&5F&XXQ|WIQqt%kaGmD%M=v2<9Ok{-WVD5Fe0Pm zoA-6iEp4F^<=>GlgWHekWA6>(%X0S2-`tD$F~*^{&bL=@_vxwiX0*HxPPAA0+h@!` zz&fU&MC-?dKiXsaHP??$M3?8renWuDk4H<+%gyp5t0Q@To;4No|h{|cR&6xL6+_}H&(d&Rkz+LXS^3Czn*}BlK?vB%YrC7 zm1jY*f|Zlj8EIzWxy}5Sz#q~*s#&q_$6QY6<1<^Hyv%!E;`!_8XZ;fVb5=)>3j!7< zf15EP_xR#B%U8Jd{8@HD@)oCgRMOiq`Bg*35j(f4(gMQ|`XI7USv>Dp{A%}42^$Tt zQgZ38-(W2&SQkq^y8qPVS9n~Y4rJSe|W*kgR858t816H%Xuvjz*`1lKY4+m3vw0WarTe( z-*PB=DB%KI`PQwPnOY8QFA8do;4OlTEQKNHJfr7XJr#^6`RqGLN15K+pPDCtwviMH zC<=#Z(e;wtPy9T*Jbn>MqHoI-{|jNF5Kr4byl~$OY9RojWJKcJp5yaLMaxg*=UmOcwO?6^PlYZJW844^X4Te zK18+z&FKoRVD$sJ&EQO_LIE1o&Dj{gyL+S1eb0uqcC$EGO9>!irZO3{?@}nDZ&|r_ zbLO`+qtgw~e@uNm&F%*%ejv9Hj8SGti!u%=G$Uwh`uPt#=2sGY9_1|6IBK)&S3 z1FIQ^qWb+ZDN{p3<>p(H9%5Z|2J@=qG(%KkM`Zm*X5p_(u)2eG& zEyJ;SK(|#+N!2a*PJ_>G-zRDk-=9sqyY5snPJQA5rnJgws-Bq6oL}4a@T}5#NzeC+ z(~hO$)b$)JdFv-pD(+3vcyhU|z*{P%O>OUjWjM8TBHRyrnWFlY-y~(fRa;BiecQu5 z5LLBTHux(0{!$%1TDRd86N)f>xMa$-EK39pAll#JgP*AHV!}&AR2w!{5w$ zpvn>9E}t2t`v2L6|$=`hEX7@q(a^7z`oV~S&zc6hqXKNA!r~cIgaS5mX?|L9yK7-0Y**H)w zDN6>8(z04B4oS<34oOo#Z#7EfOGqU0^Zv0oYLj^PTWcJ8hKNi6y6!_wfmy!1Gx#2c zq9Q+Mbfhd`7yY~QTFS3Y7LYXqRYHSHhg>_mxH3#TaYBcz;+B;lGDXf5KM@A$2~fS- zG*pKM-iM;5T3kz80n>`b;#&K$Et%FVOD4zKUkG=snH)acyTkU@{|bD+T{Teb zL|3Cv;?_R#dgu6N^y#whi2Y;Q2_P#A%>?dV3JRhJJTzVQf5B$jkYBqiM0DV08UYlC zkxha80>&ipC=}g=%>LzD5%jITEWD$mW;*1b%L9J*DsLgGh!#;&&Ca*ZUiaeqKA$Vs zuQ-Zy6VM2Fy^*8#-*Y3bl&C%?EF<9%62C$p;) zPCHNX-caU|BZ6);ru{G98SC@hY%sPEJ&!OzijA@vaNnkEA`MU7!=nNoU}dUfz`X6s zdO%c7ql1(=PdQcXKEkO?xNY{Vr zCV_|@s5U50Voal>=jOk=aqhDELDu)X6oK0`clYT6?)b6OV>y!?{3lI-VAw)d z@Ft3^(zjNZ)FoOK4`3fKh4rRL>M|gV#4&Pj#Gk6BKWZ` zP|v`9&&{~tkIe`WLOrC7@K3P06y=Zr2Hh$}tw=c-B)bdZaOzkLT8H5l?SfPscjzbe z4F;{msQSAhH64TIU=YriyBkuMVz`?aZW#ti?|~4G8?&d68js;pF={ymVVwl8FlZA7 z4c!YNT;J@UP|jW`i}T&Ua5yBj4?=kF?J+0|la0q9yjSJ>paf=r@Djt}n)UWWTpEV+ z*$;7e|FSV$CWgBPaSj+?CwtutB!7NXLKxR(a-3UiG~3BClpDR!a~%9;$mD4Y{Gp)- z=90CzT>SwvAaej3dOfCaOGYrCwpj`$bd`L6OARnd*WMVj6 z%M}d5he;@hRD4Jy${`LnQ8WhOxV=B2rx5BL$cK0&-_0}L%G7r;4X?qvweDbFXD(qlyx|^*bHL)Mlijim zLGq()tgXB~CppZIwiHgVwTYW%Ewm1B3W@NZKNGe$a626vH$2nV1fwcobIY+tA}jh- z@2co)?lFeL?V)}M;_z-wJp^&M-PT|@+{NcH9B!>|7=+uGa~MK+_d^atK3q!)2I15u z48lW3^O~g{^Nt zc~$IIC^7o*eS6zmOyE!{UVfbziQh0c_YdJkM-x%Y+P2Q=E1sDhI`x^`jEcs?3+6Nn z%|E|t;UcYoSERgf-0$xU;-SvZn~_h?B{XZ9W%7iZiu7u zJ-hDu$HR3Gdd=8^Cvy=@=8K33tWjUg0Osz@VLL}PeX?_oJM8 z8?D88BX>-Fh*P(WhdadW-O7rfG?~F5bwMOJoy;&5L+^ea%-tjfg^h_Js-Y+P{!PDmieO6x^iP{IOwjEmFGiAQ#D#T{nl=ODY zU}qKnUYy-4o`kwchu*TtdCl+~VH03HLuSM}T>kq%H2-smAMIz=>2{}SPM)`YwtG_k zs|;McKnL0SH@)xcyZgSRX^a>HlaCiisBd0(r}}ypE}fwZ^Va6OaqcA}_xYka5)!XZ z9>KOK(DH@#)U`j7qS7Om@0&R821J#OOsc-S!SwR5H`$y~^*Qe#>SO9;j@4?@dG*;_ z=%14VlpyMaL^XE2*zZ1`um4RWO7Wbqd9R656 zd!<6v_SrWsm0X9l=Q2>A`>C!28w=$|D=D|N%KMtNLR9qBtFA+rmR4Lp_5Sn2!n18i z2F$TBW`41OA$)Vcz%U6kzFl3*L)Ex`QI_&Z<;v4X3$<~53@Ge2EG?YC-nWpl{sq%y zV=7)LUHJlJh0sJMOb7VUIfs9)b=o9x643L=I-;JP^f($a9JJUG9Y91TSzXHl&t!sV zbG{&+i0W1bzB3%MgMBjgCgQkZWd~?GOm9K=#CtW>ds1!?3ruE&rlc+b%}40>0rJ(z z5o8{xYw5F?{k5DmXyQ%(5cSMsf!29+>6R`egT(~*kI_R#nn&ruL`9jfmqXeXtd9&R zFWYFJvbhaeXK4XC4%35y)Jb}bqW9x;JK@Y&-R0Wf-LIi8hR8aOm@N8ol5S3>tKHVr zx-$H0g44(2DY6Mucnv`J41F#gJ*Xg;@aDjiV~g*c91icij|P<)WOs1(EL|25F451? zYYWqK0|*D})DD{^&Tp-oLjWyz=#D_gjwlVZF4I-ff%Bti{AKzO=|Lrce{;D^w5o>g zhM)uQfq@sb^zcC!z(fJJ=x+$5l1=dXn0FmLC+YB^W0}L_(*hpou$;rzGTq*6#L5S= zC=#m^0wc`u8WALm7sihY59Gtl5FQ)lFNlWsAijT1?=CIO674)hd#H2p%66!*ll|@# z5y`*~>3?!ECD2O}9~#}B>CNKCfC`tiDb=q&U@L$<#xTyVo#1fyth?ra_y)4A%=g-& z#}KD&!HJmtvfhLf^s?O8acBob30G`)3RB{vSh|Fdx*OgBPOY)-!0J<`mgfwCxl*j{@tMA{UZPbgbE2 z_$fa>TR#CTytBmXWnk5ewH2&gvf)?>{lS5DvbF*deM<$+68oPMeP%9q+BtXYYyyZ} zEolNM9ppJ8w1OH!0q^Icu$10b$YS~V@vXVAUWntzvbAM0VM!iKD70d;tpTb`QSEz) z_hqpG?-p4N6`i~OD)Rik9q}15hZb2ySODZQ)l_uQIw;V~vzlYra!lUW`LQdbix0o|X9LR^d9Vh_#?sH01FN*Gt^RIDCgU7+2-<%2c#x>`gUaN?3zi%^B7ld* zAL*td?PfAZ9g@t}R__abSE|br*;lR5MO(YeC{)H~E z%f3tySxr_z%b#DfzaDgzgfK|rpbb z`)@~p84KuspsEj!fsP=!$EDLuo+@&dq3o3d9r^~QTK^&rto{%OzOWL|EIN!A5j{UV z2Alr|$3%HG5ysiSjfvsnm>8^gP<$ZYWlcpoDij|Y{8(nPA}ty1((&D`+AOrJaciV}kZYUm8AXoV{6lO$Sb zvf}IR4ZMpchYxgEqnB!M2DqA*1=qD`si^;%ue&B>7z$@i(rPkUyK)m~m!rYcRUW3Dh9(({n3I-qm^$-vlkxfs_0(kqJ+J5aj>-AiRqX5l z69^BhUW>dvYJb%0-oDiLIUazn|NDYGUz?VQjK+BARmYyVdAZ4IWF0dhnq>rn)sxH0ki-=Fl;rj}((y7oNgZ ztN&6xPzs=ywBX;4kO!Q;V=7C=Y+fX3#oQI{qVj3x^_B1|P}p1UFO`Etz76c^fKnjb zDJnB-X6*FKT;#sl?iIC%w-31jTI6YFLw?iSzkmoz{XsESYvhFqwml|k+0aX^51w=thj(^}(-Is|=mb&G(g)-0rgG9dI761h-iUvvs z+3Qz*8LHB9uF}YAQI#j~XoU|CB&h+5S@KKKrkE__kM^u1w9@<6s(uXD!C$+vBX(8L zu18yr+HaR@=y;LbnO!j9v5V1dV==w`f}E=8`cNAD(&ml%fnU5B#wFQr8^%w#yK642 zEs}$wyH^1fy)dMyOQ9n>yk9dV-7{uK?GHY5gDTk@vW9Yszjc(}HsoJO^+gI7#EFs^-^ z(dc(CcAte`0JDTr?a$@qz)5?`5>(bdd7_@O{iY7?%EUWUKTHSAAbAULR+BC#TEnDC zNrFS;C;K*!F{AsOw0^B^d%b)3)~0<=4|ka&7w|0)<=^%jejd06$6$#nz^LDw zRp0gorfkg{>9l2LZ7X&*OqG(=92bjcFx9_c?|szzQTy*u;1T?ar1M(&;?iM7TP|r5 zKTaEuKWhE=OZrOzw^5WJY+nO@b-|j}h;kRJc%ZY_b!F-?k~rH9%S3R)Wg3EtLN>Hj zHSjk*(=L~+GZHMvZlaC&zkyxiLb++`YmdAuMioa(nD1TmT5!7fP&&RaUQ2>2(4=sE z9TUmtRZXbyEANi;E8rF*uL9Ohq^uwnK{ilpM-%8xn)BA^vap&R?(Vg(_?5zhYfaci zt4(x?75=q;3* zD#Wy}cBL4}JjB^!Buh#1F<;ra2R1%0p7TUNyRGdnz&Jx#xP=ix}(J7Q*7nCw28VoJJ?DNUYCF(lo? zpxu)x`eNKI42M&{K^z5xI@vv{@Gce4lmfq~I)$PlCi_gG==G|ZLNOpcz|>@np{c3f zY{=ZG;Qs!J`qm-4HLnOVF-j-phNhYnGX2{Aa@opDfjuL0RaQxxiK!*B1Bw;J zk<>SqaY@qE%sY>#v|XEJeSY7b3w3Q+jX;qGlK+RG#il|01y%x8N_x7`-Q*3;e#7Xt z^^bSbc6Lm_1i#5aXXk0v?G~I<|MD!dVNX^ z-)&362Gc@CHula4KP!%Z1_5%|dPPf9hGj#P>DKsXsy*T+n&qI#pc?7kW^x-0imooX zK+`-SGf$z&x<)MWRvK2g_OCVe4@rW%!V>6^y-wffe)62Y+pYG|*LyFni3Lg&VY%?H zstfzyDv~>J&g>PKy`tl}n@MLod;KxF4H+jB#8I55 zh^&VKNk0pgd%0Id&$zW$efJO&gGuO-d^tLz78$Yi~!k@odDv~o`M(pj*!aeU!pRRmK z-d1+u6S>sRL_FF(N=O#Y-%t6-g>yQrzt}zwZG4~mx#E$hnD+`5MrwJn@tW{S7G0-I zr`p$@O|V4rdz}YXxlx8u#6j0C1-}=Em+;P8Aup{`EY0lDqN?OlUAH?IzT6WaW_khI z-6(9Cn|PR3!K2NwFc*z+r=T)e2I^!dQ^A2nFy)0q6r~#Hs*{4%yX9T*3Q3VWMWvVT znH11qP&`F zd5bK4<(fCFuzUFzbIYf9i>b$`plLi!MYVXhLudAi`t$JJ%%Im@?o$Or{V6jYIO}*zV zI%??%&+egH5(C%W@KUM6srQxPW_VVzs=@fGc26eKZ(~+IPArIcKO?0D4tII5&_f*AVlXSa`a7uNZdi|1|>&iRgm_4QR$1U!GBZxctzG}%M zDSCmQ&zI<{`qK5d-Fl?3y7cH+9(4XLIutSGGzv{tS*$!nO#+?ssyIQ$eOk85 zYo(Hu(amogaM>4tK7(SKB>tn31~WzXV!?{Uu2K7x(x0~$o|_PdO>>H;d#IH%&pSS( zTSXHWnL*UWBRl6$O;vZWS(%pO8gGZ~u}7y*GhCnEaW$gCGGRe*%2SAHZN2;8nACga zAD7R!={qZ`K$M5msXCnlp}yKqAI@&g3{xVliiV#>J2``rNGiZqFr#Nu`04{oQ`h5t zE!2V^RKnh%K_ZVLulkg`YyDS)*6{aX=N;3eTXHv{a+py>{@}aQBkgpHL zk&=%msM?{}L#|9y1ZR9GS|kh!T6`$ONxLx-+AJ7RI5ct=e7yXBFr z-xo^QMySM~QrI*+sTXM?n}6pGh8Hb-Lj^HBK3pV7pfO`hk2AL)mvo}lc@)jm&s%{D zT~I;RIfnBWy7Qu9@WU|)RKjUpMHA4OO_3#S$GT`S8zv(h;?1TE6+S3BgD|dITT$|DTX7= zr5KAL=3KbK!*Mg_Qp`9Q)X6@LBkZ<9uq;KYbeCW&v_{nq8)~mB82Y#d|$K05^AZgBBvGf{)zrBqC zgMUCd^7amFsntzKO8TW6@hTPHcj+G=fn{|c#v+GJ$FvTAmTgthm?^`TLtS8>4jGlYh zZ-6pc?dS+?{XrK#woQAW38GBKB=fqDK4qgYJ~v!F`p~rH$kdSlAC}=eb-|!~6zkm3-3PwKh9u z+KL4q>J7TfCo$++iMUG-;T-qdt3W^X@Ywt6PH%N3X|*XPGc|p--}<5{)_)Fr;TW(Z zCU(%kTOs1y3y;YQ2J-i7NdjFz=aqRCe_3+t+Ol(--6G5}!K6-ho8I7yjS_=y)QDTT zA%kodxAMnu>!}=Bc6NST`VyqzfYR=|AhA#td_nlr)gTFYHz=w9#Bdwi^- z?#Fe{j>&b`i={47|9A~oV$j7bu@{s74|{I{PF45*jXNCE;heM2!7(JW2J<{b=ExKp zXyzDGM8-5A6%`pumQqooSsFN{gwj)~3@Oqe%_R-eJpR{Sn{cR}=llD<@Be+T_o>Tu zt+Up;?{%+XuYK>e_x<@OleTd^8x3wI0kiMYnSZwK#lpxpNyR6wT~;9kJ4X)*^yC>5 zg+x@nDn!~m;CX7WeoS57Zrc3Lwe?DxSHm5ezdj{oj^oc&O`k`V)08$pemHYiQW=kw zzZq#Ou&$$x05g1`kBV=EAk2qiPNWX`P*h~NV}OXw@`o5i_3xfVmj(%J3QcCrqIs!e zm!_Is>xw=y=jcYEY_#9*U_l3Y5eddm;~|#7i4B|Bgg4>Crf7;UoDpD{wQNem$2lFl z4poU%qmmtL%2>k1sb|BdHo^l+6$Ol?V&Xv7m%^mxAk~S2q^&MtL14QtMFvdsg->%t zei=WojuaCEiLff+pyl{NJ-Dj|k<-|C7#|DXAGjl5i1H2096ySp)Z7Jfo)@N7J%8i7 zYhBvC(XK>cJ`TAyaRrI6m22}B~-}ec;0D0=*FScD?G))^A!ByBjRyn@j+J* z9xo;VBK@IdC-B0x{_r^qrE2_PuTkoyKlA`mN+kd)uEguP1VD8twLAbCa2n6s9RLkL zsha`N6~vnW0)Z4=B4ri`ogtLx69}Cllu8eThi=>Pde;LfrW8EYqqj#8-Z(u%;W@NU z5cI-{%5u8b}C(;_4-`%e7>@xkbI;N z$y#f|<4+{pfy)vlu&G=?DyB(7*_H21t7<)q6nrP`d{4@#Hz#Uj2tr49$7mWnlAcGA z7a}s7`B4NuoHOoNOQL)S6>OeIQI*C{5M|>4X;He%xcx-uhKW!w4f`yb?lyzhjsR-QL4<&XlpvaT)Oi;G~K5--2*9)LkHF%zU z2%PmZc*-?|qVIDS2_I5|*MF^s%rYfeJK=p5o3+95F9O^Te|@L(#4gGmHuf1kfi`h} z9t8?~;lpguG6&&KefhfiT0h8fb9ZYKkE{C>xlTFzj_ipRxPuRgYwhu9QsE${vrmX? zqx)c68nx@#=3^xO;~PXBCi~AM#6PH_0Acf=kE29e+);D*1i~jILDIfKZ4nMG>XTH` z<%Ghe@9t0hvH1hVF#KrQ4kWTx0AxmpNrPh{aAtb5B;nd+gu#P{hanWX-b@Z1?ted{ zz~Q>*4s6_s)HTS%y%beL&xtFl+b;0$em0BT4fd;2S9ueazK#a;5O|&Dg~FwRXMw|^ z6ypSuTyYOc`wY~eChKym{oqF*w*{r zrLzm}^PTjZx4;QzJrheAmnNc>d*tH7y!(FR@sF**+52M*Vy{Ka&VTKdcy|Z>@m6&3 z!o$wn7OtK}*S~otf^&gOletgt#Heu#e+HP=&ItjM>?twj-ww4sy%pXPU!t5W%vNy4X2Efu^zd+ zb^86ciRaH40Yz-`=>xq31dk6$Q>k*mDup5-Zt&V7^@+;nSXx`Gi2we_;z>GO?SP>FRaT~$VP)v^~s8y;ZiHr!B%YmrkXUi@6>&kj65he*!UH0tSSOl77U#AdZyn@C!ZQJ+D04ksNnAuY?nn93ar%ogjGaE(`p6^O z=~2)Dnfq*^!U6x3iF4Q0PIi}=+;$9?c8{XyiYH<;waEJ%59-rOSHti1w=d!bANHb- z64!e^d3544k2V>Lxyi1x1FKOEp;q!}tvP81hlP{rc%}ymfNk5n7{dfB&hD>V;mt=|A;q zp-a-#wJgR-<&=U}aoC15>w9Xb7am!3N$H;3mm;RDo6>SnuTH03|4#iLq@n6L86@Jx z6|ENzC8T${y0w-qeRNG$l^CXm&m?dti_RK1XLV1lBU)8fy~2H$TaYZ08J|s;6puZtw%o_e>Ltxw#AN^RqdSq=hnG-B zTD;^FatjNx&u!RO^WwtwvQ>oHd8VX{f%f17KDA3DO7mV+V8lF=53@t->QK8LNzmUw z+7go}65_^jPu!!DXITZkn(xS1y7CmVAaO2LL7YxHve@p*i}@5Ut(Mb9e&*;hb=yQ& z5r4j~EzS;k&n==tbf+-g+vZ7F;&5Lr3z z2k-voV|AXP#gq5*%^TWbiW6fb?TxCQ1-Ie+r`j1mhdIeyfU;%{oClciqj6uVqp5dY5dq!#i`bp5C-@i*+}Z?9YB zWZEg1G#QnU0;tl`GZn7{=PHn{$z54KMZ3%|yBJy3S;Gg?{9v)b=(|tO=~hZ@pE3&j z#$!@UG9bAlT>9d9#3Au zC?A#GXe{rfP}C9#4+Z~{Uft}l)ltP;j=Q>o=A4n0#6L*Zx9@y%GU)kvmwIpKfEJHq zL%*bNhcOBM`^9`teSR=~SJtDHeaL*=w<`{{No-QMGy2QgHE!Voof-Hy$9G+@#5peK zBAOoW_Ick$vwtEfl9hcD4{hJgJJ4B(l=@cC`OK{0V)ca`PM=QIZi00PXCV(yA!lJL z%Q8i_^PAoDAZ)Hi!4#7myaYK5?I-z*)@;-3Je=Gkb$R5we7pqTcp;fVzq7!72SSRG zv%r1tMJ32tIKNjc<~{k|6U}QLYah?p*G4G5Vm!!M;IQ3-MuqS}4Dzm}HSMIJ8+|7*;Mx45<*y*v}bSvb9Q=ci(7a;zvzJWFY# zcRp$hISVDYZsaTgJcXPEltRt|(vO@4ltRt|Dn-r$N+D+fFXeU?W~IHEwu_g5O2;F`U>i=&WM9E@(yc8sO>-gUFH%Y%*W*`tYkkLJAb9r)xyg8 z^HdkFT8hj^z5+4>`3gE6$+Y54mT9WnXTO?VnXiHBAYb9#LeVM)g|);|%k$RL>}E#a zk_E_Dc<*GjXtq|^^^pn=kE4&!E~ABne1$t-y=^b&ziW|mO^Pn%aaw>%kgpKyELXgd z_Nl9Q*SascM$R-v|^UjSsc#gOEs@)n;?UlMh*h5fp8GYK`ZaNHqRq%M?YTr8fEkv?wUDj z*Cdj58?AdD|4IE&sZ9ytAZ&S=qR*?IZTV1S{-v$7IQVjqOsL`o67m zB`QGMxs`qgW!|9 zN1>OkGOIrHllST6`)EDF+xa@l?K&@S#BpH}B z`E*m3O({N)mWdQui_d#%^27uV?#Z8}{lxl?tvR9b$|ll~o4n8VY_L(VJ9D*&g4E0d z;i~Dl4s?^pwGm5>hwL!v#<=6nXZ9ESo`_Q#3zwV#?d8?D)=RAJ30}_Pxpb$YH~C%P z?4CST^jjRu*s;@a^{Q*@kUIP(Uurc*L&BrnV~0T34bQ^B)%Z>RWm8meC_4(i{nLgF zJg#B>0h*t$Rh0hTW!CA0>H%h?4-MB3p292&ra zE$jy7E~nBs7fglMP(XS%RbAd+AHwspeT@x_OiT=YjdhJobprzo*ny_HFwdV2tW6n` z11Lcbr&6V{HSVcDlAYaD%bTWed;g{(66}~MYyvDpgdM*e_;qK|P z36V0O##`9L+|a;8AM%!%_!$Ji04ZI4i2DsAdJLdSe_aC;zd-G%2=>Crz_5tW0DtX4 zKrS$7q%@T(gYh?pxrkJ2y~l(XY}$6-^a?1*pqg-2P7!t%1z%Rc0b%Qy1i;V~Hak$? z&{W@0$5_t<2K@L2`s2~`HCIt56@i=ak4Z$_W;6& zLG$H|jr9Uebd60+O(E!v4kTTHAnyKbeItmuZt4eNZJLBtR0tTC!P=I+i=A1h@;a2U zCgSkdM-fjsP@6jmg18*MDWk!G^V)(yuQlZU#$*o0K8}c92-jTzcC+57yzEVl&3MNI zg92&SCXl5psLp{?dLD*%;IPMkXCl=GPb3aU-KT$%Q2K3|i>&wtx!SW1l)F>Fy;z#Y z^#8$ZBCzW|?O&eV%&_Ks6kRcHH_sYdK^9>6^Qr?lsTQ2DBw>~aXxhvF_hwdQn3=^! z=|@;Dc%D-^dWPhLk8?&Z1B}%W0_kr|rX(?$7}y}BJh63Z-kQm*>UDdxWsw3->3ZR% zy!zbHtQ{pdJH^|>mG}qX+)&d2q_`n>uT6BG6aSx0v+yI3>F zaY|du$rJd-Rqz_A(92a5{VG3qgJI^)gZ^{Hg>e3yO7*WV zK1@YS&~~}9k|Ae8xC%Y&2_I?u{D7$7z~BIX&3*x}@C(h5bEStCO-HL*4sLUPMh5;6 z?wsv!q~{;#tK+Y0Y8WWnAC({E^0o^)NgUti)-}44PlozotU=2j#Sv+ zXmFr^fFIn(3FLPmi_r}ejP!hUjf~kkzPfBvT|FZ`zd(OKUsF>*eEWs*kbb}0z6LIM z*xlKUlCN&7R7X$LDh|+2svvFU+U*8be@fEEKdOr4IPuYMi-K#PArQDRWKi(aGcfkm zF)-Hm4+u0gG|=&5Lly{=K(=2X5zuKn%*DWm-JqE^%BPy1`CJqy^zw|$I?}eEogSt> zux{pqxKaUu0dUXuHDsF_1;R0a91MnRe_vB0V;w&Tm4Az=B12p%d>ALAJcd^*_ z$?4}cwgkX;;59E85&}dyx^jS|1%xEqjM4AHEf8Nv9Jr2JDSvwj4Zj6)uOPGq0=QTl zI4@-BiNrqmcUH`Awn6R%gSs>0zqKretO{Zu4CSL+a9Z@}a39<;MF^sD{dwkx-3GZQ z{_jpLt8W(^JhKS;o&%?H1&PA)gBIGBOhmfC8ia>tCB@prxs)vxDkY@$WI&a6heMNS`hLRN+IMY)Gk7P%EId*J7ke`tC2>B@u zFGa{t$SQ>Vl#1sek)Vp z%0u8ysJ{rj30Z}}n~((vya{PR;7!O<1m1+KLf}ovDg@qyBoKHLD#gK@2wUWEgeBAl z!e3g8TaWOUkUoUJgd}kMB{H(g1cysPeM1mR$N~hRgd`Az5?T=mLJ9Q$5QGxaj3AUyDS}W!r3gX^^$3ShB1UpeIkw62DbxaExQY?p zM`QqGWm*o|Rj0S$e}6tg~s;^0QI^Uxxp zY_cYdN=DBUI4~q1D+&mTFH`-!ex{FXqCxV6xRE6@0=(aaF9ea zuCRlwjw3@(;ka%m7t;X$7$y9c^=DK_?q`YKR@4_*w4I;yx%Sa()8zP@@jHoDI`@#( z&Hr<3Ncc!m-Aa~%ow(>4VX3ocDlfm;nYW2^&MhL;hwX%&@X1rP1Lmz{F;4VB@;YIX zOaIsbICf&36b`vv5Y%`u{(}LO8ajN6yCE4Kq(HRfG**$nv4CO+`ETJc4l{64*pOlC zUc#$k_8)_35(6ep=Nc}s$WId532Wqy6RI9`o)fXkcJ2v0AFk9g4QOmE0HE**k zL9NNnsYjGT7;l_fQ~osX{tv+mGQC!{kN0_UDeMqYn)De_YfgW_5yKchDvCarA48CjlyUH9-E^oh zb28=)rp|!Il*VB`;Pni=rT-N2bxdXc+T}TuCGL(jc#;&W2C?P`LB2vz6A@qi_z^v4 zizXSi*-LKwauV`^LiQ%05Jz(Y?Jr>4IHn1(ijZ&sPL^<7tZHEYN~hsNYY9z8L;HOi z<^}H9K$BlL2)ct{TUbxq3!4mt?eM%EaNu7rfJ3mw4i16)bj%ZIO@IX*2@)1SVFpxH zf&W}M1CICG?ON z{&#?JKV~?*Eo|r9;=QiDeFUqawHFxo+!tu?{daIRq3zGv7p{8tKB?6k3=~_=#uWJX zRLp~{^$ud1K%qp{6pY@8VQ?2c^EBtebM>pO)~sYX`MWl$pnERlUkWTCJAju<;Pllx z^=YLapxc4%9&pIUw~_7Pa)(1^HyI9D)h28fxHTEpi&#NZ2c9{YJ5+2t|8>=;^^8Sr zHzv{(R*abj7V$%PQ-vvv#7*7IJwPuA%K_=%;c7xcCl8aYz~-s=d>nzPr{S<^<7xO} z2!_fVr@>}!-XWWU*TJw=FHb1;CXhA-xI{3mfi0OSMqE`^TQPreKI~q^+h^I{xliIx zzf{}O9QS~?2P~2$>jH%y<|B+NUH~tGG zUE6I^{0p{edlX%uOgS(^$LJrDbPd84{v4yTfh9G2>B#1zftJ&P_LR zJsE zHh{97ZwO^Od;^XvqH|T|G^LRLB4ON=?VwazNrsa>jVw&UxY^plAsMBSpw5#ViE&f4 zZ~8`-2LZXTldsI-{mN$wSM2*cn6SFh#HBjmibDdFdi>N7!8 zD0GV53PndL@L)or`|8h!S6qzLUT~y6Zb?GjZJe&TK^axO#97wNXeE(vJXq6;kmtms zjF%VNNhnCMMDEv{e9^hvb$?B3P76a>a_ZHUJ0}|!O0-H{k>;7KsnrTaAjT{h*TPV- zCVpJN-`PAlXSn2Svd4GZtw&E;y=lT<#ROK(th0{`Px?EmWq+p|+^(Yr{Ld}aB`iEQqWWcpxrD_^ zF-g7P>Z<&|X`$TjEcDg|?R8YY|GABNgpHM*PEtFy1t%>^sTQFgYDf+rZlpLgFIe(g}0AmdY5M`eG3yJ67|qi8IjM%F;%nGQSoD^?Zzs z-{?2=HtN$qLr?JP&P!!sI*YNcriOa~{raVb)el9%-M7>n94Jl%d~%_PNFX~SoU5O! z)1NF>Y|1#xUdP;wfR&I5s1n%OLEVLIcC46YxiTk2qt4*1bObpPo_RnVHaHIAVDmld z9_&?PXz(%_7xw+8ucyYY-!%=#?0SuPOs1`zk*@iq=$nabX=LF$4g}+}q+@KJF$*O# zo^m|J?P=n4Y5WKZDl4T^F>v<{RK{;F+_~#yxBdIv^cyj<7qxNts9t=!wjSystTWQ} zP@ZXg7@61N#QIe2353N~2UsJ({kPN-Y}E0SD*UZdWi26}Pj3>l8by?czk}cJKYB~u zC8#O5?y25ek^m9P*vTG)9x-; zln9tJd*EwKOaF0ve?2Ry$wl{&E@A6WJs(J`A5AaI&*B3){J4pSVZ@Gv5 zx>YgVX;PIlQ#kM0SJc-0>KcDD|P4-_?42?=BS4--TbsiKSjCp51QIlSe8ib+h=3lICp#u6_`w~6Z< z*P!w@G<@lA(DL`yBXFL_DA+jBuw)ZwvG<9O*qn92t5$sW7a=|DZ&>3WXzK1a!0@+X zM6=;NUj^@vDq30kK91$+@OCZfW`DCIe=85%zF`>%n332lnn)p~GH>rz)M)22g&efh za=v4|xQ%TesFhgsH@Epx4JLAH-M4(0y65mA(7`E>J*1FCZkqFUlR_d4{>LqZdLl z>j+{A^v%T`8$30;+8)Zv zLivgJ^%~U}z>nOAPw*27LB(rcz(QAcczE!_ph&_{W zMCd!(HifUe)CR8#b!zRaf6To;N(CWBvWDu!MF2yHkvYK3-T@f^z<^ZFh#lLdx@XU~ zRU4mPj~XlPh0Gj?=*dOUIYjLc+ytcd$&EWlE(SlF{ibo*J1^m<5C|M@@B`Iyi2)yD zTIK*`Q!b1Iq%%DBM|asf!{m!K9ocj1Kcx->y*UUn4Qy+tW@z+}&HzvZq%TJ!rh{kR zm0OG}BOmo}VxIPTKhnRFfN@xG+a1WiJQy%xn3cBF&*A*`;uNI{reV+u=dc=ZkIK*< z*uY>7U@VQXbr8epV46;3_UM+Zh>ycwzg=Op(4H z2{BV;PXx}AYe|_FA3e?pA&3r`s(}CuP+*u*7zCX#%qV2H5TSo^E9fd(cV8|%o#RD#_OUnws7du^fJhJHc z;&Y4FyQ5K%7_b!jdDro+JHxEMXfWc!F#Uh|)$4IP&vUIKjlwr*OGQSYU0(HXDL{VN zfa_v#Tf|TVH}T5x7n&~EwK;F%7?!x=(CcEb6!PL=j{2;KR|79A=s-FZ^lc0qeg z;qBh*LXykizQF_cE>eAR?m~Pzh&n4t+%~^$(w|)Ra(`}N`g&8*%`80vRTeJ?{x7io z*gpH$j<-dsUdTM#raiJyHwSHG1S)OdOYAK3wD(d^v1`j$NbWQ~?=Uxd4!l?4@Vx-y z(n72QUJP=mm0x4g#eXxQPUnbZV%8@D~7!4Ffn zQ7{}BTAKi`)%=0Evamvsl>NbLZIrI%co|N`^QQ#%wEHO7Orr#&5Tlw!Z@v8`Ji#Qh z!YL-}Tvac)Ee>AWvV)NhIxHwmqRV&Nr-^}8uXwz-yUfVmbA?2}+~VcHU5mC;#WfCx z5*p~0`D!fUJaC+LHY-^MGuN_~CHy^2fIXv&Gq|gN15HwlEkNNJooqUwEM_1-IT1eo z)A@>(+f%}+xOwsHjko6W8iG!c9utFz>sS1v4p5&5pUm*O3jKA71@}xXcyCD!jjt;m zoSx!vvL0k@WoV^$04O|! z0IqdoX=j+G2Pd9izUa>EH%AZ5K7Q@oNJvD4<{j2h1+Ud$jhw03S}Iec{)&wGHcGJc z++5Q2pPf<#EDc&YHqX@SPQV71e)(QslJMGY_&%hI?grE~X{Rw&UglxaKAWpkPu(?~ zV|;TS?v81Lf-brOpvg&!klqvamUhvxQ7X~>NL_s{l;x7UNiR-U(@X^}d@9S`pVwy| zu0eF50C3f!ahQo(r_#?HEu&n`so!xp&vA0!1jFizz+anIi0ybVYMtFPAvZ7~r%LM4 zavoxR`e?%^!@LcQLm2bmynQR~oc6kwZJvcN}|wi8R9+g7~zugwKh z!j7xj-FZ2clO@YoBLXS#@H>^`pvQtfL;@AU9YbZqJBrm&V?LxQPECD&NjV(5P3|Jy zN6^Ry!$2edNg3gvI{ZUJh*d^^zj99Tl7P?=Wsj@{>J(nXJpd=A()&kd;HuuD(Xjc2 z5m695ZctJGW7GTvl9pa;o^Ng5?vop;c}ySdXkuyW4yxQgs=w6p&ztXks?_DV<#uYf zdA-CU)j9GpHrPLI9_b;PxBdrK8xZ5q;Wtcjp?E2+n0q5JW7{E6(8SXDC!I%X{(0vO ziPmSjq(;mwsa%vBtTC&sb(|yW{P1|*Dv`sDy&*bUvike|CfaS4_E2*!e>F+y3$^ph z`^Ic^%B1%9wGfWRu=R-sg5FW>UU-+!+FVet%j~H&Al$8A3Jg1de_(Y(ck#cp#QB0f zn-xbEr7Aq$+dX2q0sOrl!cQ1%Sua8-YQZeMqGRnze@@|i)BAq&y>~-D0lz?*120gb zKJmyjNE30d>brc1p^+^OlhXcZ(qy+SbIi-!_!6|+$^StN)Er=0a}){~-Xx5{DP%1< zBXNKIhWfi5#ub+de?Y;3BE`Yf7&A!Ns76=M@IxJ!cUYRomXcQmfNp(;H7Bu<(MZzf zrdYa#sDBzaNW-@#h8b8GJMU3d!Y7*2+mlB{~myR_YR3fKH*XHaKWYlPANw%1h8_OY@q~&Cqlf zX)>UD|WO%!iHtL86$$Y>74M_G^LqP#mJal_eLgeq^^|W6$;%Y89CH| zb62jtby0q7T#@FoI_bgFo3Fd^7d;q4^4Bj*HEM%dPf8=4lv}80w!9u=mdpCS9A+gIClz=psdY9^oU|+DqtSbq1-}|N z%9(vx(4~6Pn)y$k7T~&q^|jZFG%P+AbG8wD@FtxnNXg{=d^*W@q@QM|GMQAlSx^J(z3z;{?J`~Ub3i+q%)0(931D}k5$MX4Nt{i0++lGDG$ zN&in1CvhNg(n!)jSqA?yC&93efd3bmlK^B+>fku?QpH66|701J!>AK6ZkADkN#uW= z`w!Vm@0n6#I6f|PQQm*~r2gfT`j=0N?_WNtfBB@ip003?s{bW?Qqm+qH=&ySub_{@ zX{i|8WK{!*tT~OQ)Nv$@z;{Kj7CdH@*Ft)nmsdNzZ|C3$PU*=4(1*X*DYuEV{G?Uq zMy(agm3OA#1XF-~64pT6Qc_g~MzsRz5?e(^THI^hXPaM+WY6e zswI(vv{EVni=ku0Pm%Fdb8;bG#d$o{m`LTv&`pWd?HIZ~QSbA3sv41J9ZR<(<4u5Y z3+h<%7F>3l1ywTvPxa`vdy)}GQ*N!i3^0WDUR>=n&-GYID(ztNK`%rL6}`BvS7htICyv#NLWlCMGQcuB~^t; zHT}%{vY4(yAqJ&VbpR8VAkeU)Vnl5#E2O^ zcPgRhi3&l7wN_uBJ*K(#+Nw>YBLi6pe*1|6 zJdEz0y0jw^RRxI@JH-K9yY_=U2B-cO2uz9@iaoe@93*yz=&%x2gr zI3F&y*jiz1em^&X(0W6ORQe08LzVU!*bs1UVe7@j9pkc0PKM-;-eyGT>KZ#d`&Zsw zZP_I$w7O~T1C<^q`~A@8u~gUQbS5z($dCP9#W{HQ&<^8$hRvZ zW;7$YjBjOo&mEt*k(U}MX;kD$*tafvAgx4E-v2QUaV?e9GErjp3#l zrirSBYo|^qB6bp?-;k;41kF*{u;#tl(IcJVvqj5136VWZWS{%@f8GO7+s~B80wS_3Lh#vW`ct{q2~MMFwL=k_ds5Lce5R>o6j3KPa!T@TBe; z`iWMa%lnY|&teh#8aMe`P#(QpT_P;gVV+!8wr`vuF+6J+xH<6MHl?4pw9g)+j3e1; zuAG$z#ST5VJ^%3y!k*K}o-ZW||_ zXDm^+iQmE5w*I33k(^*+C{r1vo#_3t@Q_uPA6?odnBY3jSV`nW)eZUH;W;k!D|)x) z#kCi5-`yygZ9`k7tX8Z>bhvrZaP)4?LHoBQ?A`7DqS7}w}E?=EFJJ-30+W%Z|;Kxhd})oBOj0BP1|2GiKtYe0lM8F z*t<}2`C_@%`-EoN*f?y>Sk_LIo^`~@;YN}1Um$EL9UjNxsr^go>J*|zjUL>R*me-k z-A6)_0j%t(lH}uf9r5_yImiExULFQP*L*QSO3W=x7N%?hae&cD_Zl3lwDg5M2 zHBqAJr(`4 z^JZeyGDm{$sT5h6F6GF~MEk=Mie1f=*Rb)y_E$!E*2KjrUAAffh;ODAFk;V?977 zX7~>ZdM|R0W&~eNes;`9s%d!6$Ty*cqDCdqt$?vK`qlL9Ak$1&mtI_-dx?Fv?2z=~7qb4LR&V0(^hoJ6@PfRIxRLyGta-DIo z&DBtWn2B!+xK%8?CPTfK$E$yw_20>D>H&6?#k+MiH}d$LBxD*Dd*=q15(U*=2sD1H z+lHFk(#yneMm1)He+XVq)V-^PI0mJjK8^D+3ovA4-4NcsZBJA>QJU)yd{#kl^-%va z58v^27jq^!J8fT={)OmAs#PxoA;gM5W_@*3q;1dVNY5EhJk?}2;x&NQBnak>QgbI! z=aP58l>@3LLAYL&>YhZ^>CIbB*P`HgJ$hFh2e1)J_GVW8WFI`~Mr9H*A<1-Q3jWB{ zqgO8lH%-JoIKsWJSl6AZPCO9#xIw;Rc~GS(%&bu$P}=__xhJeeyTn zUW)dM(u&JNckR}0USCXT+pRcATS9PTf1ihYd9B}f0lE0hdAV)$!f=U-HjYaW&rU*J z$*4i<{-n*IPusHH&Nbf)^urROB=qxoTw;$9>dNK+kvhXZbq5wZt2EUeoHuoGrSrOr zK7oX`8|tLPznF`@t|o+8_dYcAUpvL@J0hw4S)+?ii|^7k+4F{2(`P)nsN?#^tl z_^9xC#9X3JAH=xXN_t;}_pYI-x!9(yyj$*6?v}W9vZc13kosmZENh;%-`2j zm4Os_-E8r~ps%%r*k(hJkV02aC_vZZJ|pPz(7bjlDHQG*)hNFHp6Qsn9WZNF!2D4A zPmv>UFCpb8YpTV%^cf!BcMh3li>ysyHo~mny_HQRMUov2%CF;;RJLTmEb4}h zL1N}gGMHP4b8Se3ILxx(c$+DXkyfmlw$H6@-kB*s={YLJNuPRdns}=6R0dB9=h3}tj?DcPGskK`;BxYxTIUMd?oVyIX~Bm#3EyB3V&-6c_x<#F6x&3$N%gF9;X(dPyhDhY1L;9v+nA1x zZ<*O{wgc5Ybg6NaKQbMGZF8Hxq0jVzVB5o*j=;8uH64L%zsjl2J8~sqmC+XJYH!oB zCc^ZCgPA$FZN~?WVCF!!Be3lTrlrT*F2jwJMty2n66UUhN^r1ksS_EO1aB?eq)K*M zchY=&{M3zv?LHvfi>fJp_x$#0@7IS-q9p7nw5?s@)`gEZ0^F94V%#|Ha1OjerN+qwmx#h^|&9P37o28{Uv=BCLDIpPn+odumErfaNns&9{TYrJMYBfGq z00-Poz%}4dvw1j$kY;39bcA0(q$VPx&;e~Kp3Gxp4hL_65atTq|4-^{LeJQ!sC-^&a@dE<*eGNN` z2fKO9EYM~y&G&|H=H-Kxr$#N$LiT2gL72W@juH-bFWnsfR%ql00cRhEoq5rLlL0tDzex)u=V`Ez{s5$HKeA<%PFia^iNOd`06iCd<0@da;>owxb#rPA{S!QhrJB;x3wnOdWD@VSv`35-cZI?& z2UHhaMbg4tpy%8KDQGW5py$X81bROGrhr%+&+N@dvu*vs<@I%_^9c0Z!N*^UxH_};DdVf)|{vR)3lY!T>ryTo>Pf1fyU-o#@)8y+2$ zMkP4txpbcG^k=6(8n}Ng%v-o%VlJKN)VX33fqH&~Y1S^Y&zQe6c>d1HtsnQ`3yNS! z!Bya+%f*lqxYv3{y7ku=athW?8z)(=N46u>^Bm2%AIHZ(k_lc^Wz_XF*B>nfgnItu zEy(n|zi|8$xzgDHN~@nni^-`R`1rhUQ~DGOg7#HgnEv&A=LAzl4-HCu;*u+eoVgg zNa`XLS&dN7H$OC($}3`jNJ*KJeW2#99-1$NdhVZ{o3L%Fg|=af){LbFkElo+Vn{K$ za8GkzkLRc@hxI$drCwb^B?th0y{c#Pb(c1kGe4%x5t(=R zla^GEkk)e-%C1}|2O?_`0Q&2oBCqnAEZy45AG6<9jr2k#2msykQkJ&%w4?GOFML?n zJ$Y^;Z3qCJH6w2JRc&38e3^KR<71;)s00C^FLO#caoWgGTPSOnxQO(aWrU>@|L3@KT&=153f@YKs6|0L7u&bp_s@R7Ud zOO=!UzUoEuLaC&=H@c&Fi=W>r$4lT1rdk8Bqxejm?KC07Q7P+LjKTro`R|c`h``U! z3(?|iqAQrIU3bo2zUq1iDnaZhA3}IHXr14Yk!c*pY`C8H`X$nMxm1u~M|nDn$H40S zGq02zVIP+Y46~jKd5+?_Em>|?_jJRyWocuF z0;j)ia4))^{lV-M@JQj*A89!7N z5jsSuc^|AR6!h)g>mzA9*12}i*uzCg4Z4rhRsjVKDuojeDyAWgjmT;g@OT*Xc-oQ3 zSs!;>2XS^Ji!COBMWtdJM)6^8lS6lHN;xa(adH-a!=#Df!%>}s#&X(Hka(L$2L+Y1 zLXOp1F=aB*b*qy!8$57b%oxU7_x96#{+;lQqOV?;Ww*Wxw%Pbm)2LkcLXoc zEOiEa2F>+4p6K|O(^FbCsd?Q7k<-#%Jk|+D#F5mW&$oCbkd-SIZ9DA4bgyxhSF%Lc zi#As|&DecXZr#mN?#I;LLkkZ349%7_Xc1}D(hm2}_iqD5aZ@mEqV8d3hhHn$%L}x^ z*x8)e<0m$CHI(sifJY%M`j?@us5x#1a?sSmLhF4RXZ|@FKOY`AEY3;XD0Yj9xo@3b zGL!f0YQ@h2cW#pMqrjqkSQpKu&MBHS_+Biw2VWo?F>ZEyu;ZApKIljja{=pa)3$J` z_J|$e18rquIYT(<`)5}B_cM!elYOFDRR8rXVn;PEW?l(0w*Rtn+{?W@?>=!FJ7}i7 zQn3>rAJDmbYSXl*Vri{ksO~upN$^wS?cPA~0Zl^6ZNar0GxvEvl;-*N*r_GGG98sZ z^Z=6{&@_ZH(61gPd{L<+P>$Yo9`W$PtLM6hHrG1+dptJ4ydBy2#4PX`~II4N}hY;^X zRmWP@&r`<0EQdu+XMEkh+6SnfdEqkJzzt^IJY&nrlADvga&D9K+K@IcDVP^A@4eX= zW_r<;s~zWTrTxftcU@1o4{cGh1N6-cS1f+~o z1duYGhk%qf;VA^9jO;@|%19pqQbs8Rq>SnzAZ3(7K*}3%Sp=kQv6AY~+hfRxkl))0^~Y7GG?quD}0%Iok_1f+~g2_R)W4*@Boz9ArGqz?fp zBYg-+8Ra1$Wz;tWq>TE8fRxcpBOqmD6#`O5Jwiar$a(~%jM_y&%18nMDWhH@AZ0ZB z2uK-eK|so=6agtC`w);a8UqBRjOyV)%Jg^LXYc$3QjU^K@hWG*KS}r}#qlbaep{dh zsaFvFN^rPAoPX59i0}wsUr4qY5@bA2j~!ze;~Pp`T)b`n(y#nWzw&D?o#D9${-s|b zh;@eL9r)i$zp}f1GsGbLJ^e~|JmeQY)XFO6=<718B>>r;xma^}?*+a6;KTHrUiEmU zJk~r``O&xo6?f|fAPRn5(eeygc^9r(0RwMPt6 zJlM4cnGfmlV3HA4j1%v`EaSzN=SOt9i#XhHAJb5@H7oinL8!6NiFr*EyBT9NrbFeC zj`@c~{n;O-B+>nx1MlZc`TAljwpAa~jeoJKIIq>H{1v~M8|ZeR-{y4CsLmu(8y;2+ zx$~%0SMpo;r!=m8@W@kx${N+bfv* zTx|l9|0e%?`Jb;^-&xMO zuJ|Qc7P6Plc2C&Wc@BwhR~YskUZ3@Sjk_P5%8XJ~fA`lonJwwp8ZQ3@g%f)8G8Cb| z8!RIxruI?vcFl+^`HQ#zw*@aYP`HB^)qfkj$eA9|^lAR41+H~T?Qmw%3HOza88?U% zZj?eN+^7_taHAAD;l@kxQ=x$;+&Qymtt}x5DnDN5QEPYOz1C%17dqj-hdm18SWl?g zaDch(?Wz1xxz{>c0XpH%EIIzd+vHf#%_q}GKA+?;p-(UO3HPBF4J8$EClu9_YsYP@ z_H9MhAzIIDp3@g@y@=Atwq7ZIx>KY3)g63orn89?Zlh(vxzP$XbdQnk%SN-=g}6;U zdL{5c1W|xL(~x*1N9*9>owx1&>k;AtgfKU)=L9{~7j3KKU9%q5F46A!aS@r1PPma7 z=!AROuDb{JJF&j7FV+fiBFxNC=kXJ6@whA2SDCR5*kjh2t21fow*lVyyNfC6;u6w~ zx(T$PqynodaiA`;9BD(R+uY}^s02}ZhH0qZ7RqR9UH`b#Y1jKTcZd#U2NQ?e%7;Y4 zYmz2Ui<{^bReO2*C)5_A^u#p`bGWU(-BjqWg8ODO4NeEUxU`v`DA7RE`8L%xIej9~k(SbCQMF-Mzl)XDc z&WTNaKCZ%PUY?L0QG!!;pZR`OT1S>0b8z{5xh3i18<5fHK-z$mCof)1oRFC7tF*cAC@l%It2Bbn!;@sb|B^}URo z>&?z8=4gb|TE1u~+zPxuu(6|bxGCQs%sC{6d+5{VRR1t{*-OIa(8u=KaT~mvCgP> z=rHQ%|pya|GpLUZ>en*<$O@v0Sk@uez>9~-;5+4*$W>}|T%!J$INcW>iEj82~= zWV(t^e3rJUwQCE@F$=kgO3>-^Q|Hao$IstZ#0p}j6dtmda?KPdE4VVULVSwU4x(Bw9(wd*+xi94LN;&6s+mIaGg)vshCtrY^jBeLs(jMT&Rx5v)uD1+rp@Yn41W52Rurs|9H9u@ zMvM>zCHItMQnZTr;hz9Ut4MI&6^_Zdu@Ul|TtT_=05uYJl82{O;g2EjJofkj%`8r} z4eKPCv^mF$Zy6-Y@N&NXTtB`m`Rq=$kQ*JW%hrN?O! zz$b;y584;77K4sLOaK(PvgjZonl8>UaboFGz>`K;f+N`K{VAq~595xhL7*8(_;(n# z_ZnI8ziDAWxD6k+nk(AoPc0zfocX;C;0U!inFM!K4k(@9i?xet0DEcXavb+o4C4;T z0k3IP1H6aCa3S!(*Gk7f%P_0D~0u9nkexNSH+=y`p&)~=<202c28o)*{ z1%RYAb8#Qks<>pY2hSZlgD_`*)&d)|SEDW);lsG2W*|%@!kEIrP)GeO9i9D+TK`^vE4YRa;|{Lr!{ovnAN@CVfiK>0 zIs6&@wQnrBqi_bIzy1LY6;A91j5{m`vc0mNF}cHWpjL?XITc`R)**~L{DuQ@QAs3> zJK6@v-}*KrxaKDasxq+jhe)d8LGS{`Ax;f`6(rO548ezShuJ{vDjb7U=@%HJ{ojE; z1iAsRnbOZx@n0gT_6;a^EKVOi>O?$@#k3t$4goO4;seZEuO99yiR{Zd&roH;O1C`LNQk+X48ergan;!og zIMUv=#vRCn@N)kk22R{$EgtYzkt;^3?^5Cpc|wp+zft{t0yBQVi^GA~q`kf8j*CKv zQGD5a z*3j<%6al3!toQ-zj0+{Sck;Qzv=C9{FksmaR@~Qj?szWlWj|Pod)cGXK|SY= z`Ql#o{SrVefVGv$9q|QWQ{k;b3*_s<5Tbk=DnFnd%Nk2arjax1s(SYE;1Rs#x^b10bgOcdjew(7+fJs^gG zDtjtF2iCV1$GD@5dhaZwU|dULDhn{VTT>9-1h|Kb2>~pP#Sao5(X)mG84<9k z1eoc1SSjWcOCL05v1Sa0HSN6wxxyn&T!HlQHpH_=BB*n%ti zi1hR)_NTb~AGCn^pIK(yCig8x?l7f3piUh9l+v#l_&lZ`z_`Pe&~|`hm}-C%4_PHl z?tmrIJ;KU93{wj5ae<5L<|EdwA#qCt9x7~B4Ll2h^N$0If`lh53X?l@sSo$GuZ9qK z{e-of$sM)ShkTE!L5x!&6yAgznucPSl(}M<;9)*j3{#)^xCYulj8hd|5dMi}ijQdk z3TlFjnmecopZSLvcQ6yNbQ3#S65x3!D-YuiXF|7C9OYCIobO`AfofR>4ZI!?9VhOv zCXQ|nOPd1M0scP0alre8zRzg>98v^*a;}gfNK$~~7!Lf*;aeKgC?_TY=m` zNdFP$DxvHF8VtK(2cs_lXF&W--^Iv>afdEJbX$BJ2EL~0ABW#)`lkl;p1|{A+`&%& z*nnP5|6hAw9!}Nw{qJ&R&c$&r*F0RqHD7aykOnEDK_XpK8W5>8qDe&&)lo?_QK`@% zl_DxhMMbDgMH3-3k$!vcbE$5_yFQ=K_j!JQy#Kh*v+mk^uf6u#>+G}7dF`{--zvcQ zK8lYr_FHNyK*J`{8{1$rm4m`;#xU z-S{4xwEeclpJJZeAA=FkUEgW)E6fwOeoyWF;j^$jQlI#J62;{~rs$r=KD-RG`qVi^ ze=6K>pcg?RctW$7ATIt1+}901i>FfNs;+Zw`h4Exp@rhAy;euu;5?1V^XsEd5un)2 zT%VxRWn-UjBhsbnW0Fj~cmk=2lyQ6l*b|H%#W#Fc7Ih{rPNoTf8^VBRW%QUaZbstk z=Fe?vo1#tB2dRnV@N7OB|M!h#hgJedhbLoW%um%hGM-_3+jndpY6epkQOL9mN@0AI zM#e$^zWCTxOOA{Yw?n%}l)wnjCe!%A`f2o8d;_)JWV-iBvlh+|_?7 ziUT|HQMh5ppNryD|EZ{7w_&u^l+YBcQgd?l3~cTekJFM4 z^?3!uXc4^sea1MHcaP_XynDM)(Aq2cyuVL*7Wrq%{ZQ&W5bT62bzTPQr0FR6_nrKv zkH~K_2%_9~JQVKScNPkcZe49X=&FZCO87=!7*m_)heUSi zsA`Ec3IC?*H>uTlen@Rs9G42d*hnxZ?F23KU<@c9_F{k3gy@1h>)jL7pu`M(A3G&N zTndks0MiCl#1BbO)CsCwf}%>^0C`R4sp{kyNYNz$?|luRMoUr*$y*>VT#}+pPK8vm zBt?z<0hW>|G&)`@3Fh+}=m|G~e+8t#-$MA;3;s2Qf79XLSnwBfB!}l5zSz0O>&oMM ziG{HnuELUKQWPd3;V`7?A@9Bv^bJVaO3-LB+==_3BH$pU3cg5!MwQ`C+qW5&#)-jW zFjTy;M+}CM;LhE56A15Sosoo<6lAzF_cfx@C@CaD1;y{@p>XHzYe1C>WkF03m#4!q z!X>y9_LZSZHS(n7pKKk7dE-vp2aO9p&C%lehJe^@K*R+(6nE;r1CeOh<>J3RCj)N1 zs5N)qzFSDmL)h;dqjY^d6z+6=gta0sDgM4S$Bf)b`k?c4vuYGcX+N>h;2wa`nmaQe zaY;N=0I=z$HIeaS0{rvGD!8Y8`K-OO{V&3#4H|_zJ>P}wIG|Z3gLM8+BCbUess6q~ zO~}yPnfJ~hnQ8K*lYb%;i6q9(olEZ`AxM?9og;`bH^@IYoT$v5F7GU=T%+|(j5awL z?xcCdRn@5jW3w61xl`tSL|)mWPDEr=!wsZpf-)1h^5*rSN@;SqvlLB-&Yd;y6)HWX z#fh~x;x{l7;o@AG@-`w5FO?(ZqF8NaziLm|fX0;uZ!fVd$m1?likumDqPtC~G-c7Z zk=hOpLMI%+A|wotR-i~la_6lhmi-b@(ykvG5UOzJpd&1OK9rO=*noJ0ibl6_|9dT^&4>qKfN2a@8yzGWxW;7%qMi{$QrCr+dS zEX%$~-&wn5g17CkiHrwdz&@|(vltx&YU0ixR*U5KPa>tjXl!!ezRI0JjJS@TffQj> zin$DT{xHJP&M@$3jHV>Roi>b^{T(n zWex>mqv9qq6pAp>c{T;aN|G2hTmui6p-7Tbz)10OX__H^0n{NjI}|Dn>~&!i!oClL zK$9XDVKqF23O2yZSP81o+DGL}1b5q+s0Yu7TcB>%C5*HWZ&H(##r$3!_j?Y}?`f38vq+s>d>En5HiUuDXS|gn@f(+UC92uGdK2i=05{?Sr zE<=-|kD5I7wnE+ffOxXQhxV?@X~e#>XC5gFMtsx4;}t0U_)*vfIwt-_hNeMxi@T87 z+vQ=YA|0B%F5;R5$Ghc72k@r*R7IkVvOKUJF%{=Uz_I2o8Gvm}mZwMx6GM6t0IZch z9;Ap{Cl9OzJrl2&1$)ACpdqoT6s6N-==b#O(yoLpvbkoILEYkfCKFB<*!>8D_$nY? zoD!!q1<(dAJGhAgMNgP;!zdc?01zuq9oCT+UQaX>1>V~=L5zl9157e>bG%*wY*~>> z_!TJ(GITS1ksOVNJ17FN=+P+qUA)%ywP)6GJa*wPX-a4~Rau_FwZAdiR0)95nXIqZn3=oH7(PA-S$Rs06kkj|Y{ZY@&gyy{@wwn7COM(55eN4Q(3 zHt=W^Jg!igB1`AaFb5xTz>^uBPy|JolhFK%xgfo`v&s=yeU==Y0WBHsByxnrECz0* zLZQiUCy*oBc6#G;R48gP-09fyHw z#FIvw2I#9MH<6L)o#|IzZ1rJ zuHjA-SBhlyDE+x&FdQDr)&`=ZexLMBo_I*Ap#QlrAPc2%f1Xe><=FYT6JbFjX;~tA&BzsXf$d>(`sBa>}Az|F< z)QDGMk1?(Q1B0*pQ5)_AY5nN+S)4id6Q>>u!TtUHmpd04F{{Zm@KU8HO@fNIup}hO zMPRD&K9+sw%W~5y(WP~(6$yh=%l5p8W2H-L~y7sQlRF?}f1>4j-7B`ZX z16JQ^sV6+DhN*t+T(d9i$NK@x^KtVR0rrvJzE4|U#cREI2v|{eg*(oTlc;UCe0=!* zk!LU)8AEm6#$uz$Rs3!q{i{wEE&(j=JZt1#iCb5jaKhGl4p0XqPs*zqpL zteWZ_Bq@)Ny4Zfzo%dR7o^w0ZPl)srWa>}CL-J9M*ZEuzn6<0Uy)V}xMD&B>Un7j1 zK$Cx~z!1i;R%2lMys++^>#FrG3#HHa{j~!BEyh}SJ$GP$Ir8+%Sv+?s<7_VdwF3Vw z#wz=6WtPjYIs7BW`Y$Z`FD$wE|1VgwFy_@}O~&GN-uWB0CwfMN5wN6jJHsu? zEv~_LdWN%JQ{mpU-?A z^*Wv8UbitkYoM&|85&7u1b`*~vp&A*f&)t?rMx?=Ekz1S9f(Xih!=DS$H?k?%lCLyV?H z)N>W1*^v_%-`XRYeE^vCkwlfRrc|?A3hx8la7S^ zCC;dAuztiuY3%?~keROXdb{F*lSm2|&M4~7TQiAw(O3gYkUK`fLNZXLpW=+=2N`-& z6Fw{QytY{qq!!GERst_6euFb6xNX=iAm_O5coh3>#)MVz+fj?(;Ea(y0g18g^K0tQ zsjM`2)@-jJx){P4Zx09a1m_oT9Zpjxr$K7CI8BudEd+5!3?DB}qmYlmzn-8#m~e&^ z6%c5dtt%{z?-K_S4#MJd;xuJ4>;SJ5r>PN50Pe^nC;&M!$xvU2HZlqM0BB^A6Cp2u zsNQo(L3hHzdm1fr5~L8YIHLL*YxL?FC)*5EJ3r;mIY_u%CI?v5*`Rp1hdJri<5ija ziwu?=CA^diSZpj52&ta`aihVW@_ntV2A&hXGzcut4#H`o#rl^oKC0}K>GOLH-4eXr z`W{&H>7^nm zA+zCeK#aF!HfsEbz~cD(Zfop5+TjKte3O#Pn^Otr{ZnA^!2DU;ycYghGR3|_)Y7Eg3rTdRk`=5MHyJyxzbD*j5K%L~H8s`fUd1D(-;>*F-KHMU|G_({ z?-4b5K7P@+9o``$7=12fGVWrhP^Cdc?c=N5tphqI?$8Zyk51Zj)|$}s&k;3I-xZTr zXx>zaWtd6rt!aJBS+o8SQFGb&Jk-TiX1>Ct+}eqa!<%m--QqZiTHembiLX6xOg?M5 z;M~r_?J+gQz|5juFbZHrlXAK&+s|>k1#S z2K@q2Q`{k`xo?Jfgc-SG_QiJv8HCl+euJn@IZ=AwXG6!~IN$5@(o0FN;d%m23($Xw zsFj2|l?V43c;CCaHP5E)a?ugg;8%!R&O5Qw>kCKgk9aL;JLA>)EPKM6euk(~oE^-D zCzZ!~myNKsx^+;EaGGBtYEO$Ldpkp{Jx`_Xm`S^6tO;+3-K<|CYUF-#R{n%IqeuHz zRr%^O>j>#TMbwz1pDE`(fB%sd@Gd!dk!oHJlKg8#?M3!G&wA5>YpYeF$Bw4aDz<*3 z$xjfqTWQXhcOSFaWbbupsr2TGqr`;%6j56AIUmH2z;CIWZb)#i$%}0mmJzX%(=X<_bZ|{&`>&WmTk;E-eZposGt1J3Hje5 zYAFWS)5)bKcv$Ax#&yp1!o(B~B5LkaO~qd18m?R0sMkS6&CO)ygmEd$ zy#pIHYYvFdEhpLnMD08V?!@S>2oNC{WW&IKu^Gq7=vS5YG`R$UE@8+E=ttBoY0E~SEfE%$D0LT~}bD&VAo`U5q3bq00d(aT>$(`i(|s?@h*(q;Bn`qxB<^Du{+mj``7Zr!Qo5k zG(BO$U%rFB`0;8wjUvpsyAR=C0&;_!C}laYb%Jh>kCOwoL7QZNe`yjExmONsvCt;% ziX6>Om~i9opf71*Vzcor)Ft+;=9pB1NSl$A$hc)Ua_EGm2BEIyG7GWVvD@H*8^#=V_x8Yv4Ja4XbyMK3g2o1|lrQ*!_J))Z*2F#i z_fXej`2sBeaKhN>fwNpbO`2E2x!5>R*S@=#H@RM%|FMa>^3?Uv+C)O)Ak_6l&dX)2 zPKo&Fb4~}>D$b=4ZTsB`sOzHD!T2>2XMIKql+UP_-20liO$?Y2|WS`D1i$CT^a!Da>ACo2@bQ6%{sPl-YL;^g`p#ZWn%~mb5RBtBWIEQ77F9XHz|Wl9l9Q#qD)gE z7RMrGnlZuqpiI+_B%Hbj29Fvy);x0Nw&Ig>Rv)-n+;yd!8@%}Kn@Y~1J70#P$nE3# z{Sos1__2=8BZNc!8f}VP{IjonIB=8wg@!d&X;kKkbz3X?am@w9vidi0(^s@f@3;xPrH_1BDMh)k^d3g)D&y*tTax}GuFWBSmFgmapKzl3EcFLXj7yfwPaYf zp28C!gQ#Bb9T!&}8Z%x=zgL^MKEEO+mq2xn z6CuRpq1)xE4Ug{i`NW4CdYi261AdjHV&Ytu**8I83-(BWzcU1|+_#B;(rLXWoT_;- z{n)(w!GNVPtxTk2^J%O1LTaKTw~S2#tn;1}#~n?MLF*Q7E#&LWjRGv%SS^zcV_TBe zxb0cKLumNOA;ct>k3=br+26wte#7$i_XudM$+PNL`J}?+DQ)HfzkiMvW1iPoC&4F7 zIsgO3fDcM4TV)s3W^7qmmkv3}aiEi1JSg@_$XUdc5b%Q znl++lbNU4>*?>@-{ZLXjFSA2JPy|0vE2^u=G7B)V^f9wAGc+?ZWLcW~vn`Dc4J|Cq zeSFygMtII=A)3zIc>#X@z9Ar`)DQ9_DAptuw|1fR{e84Pqch5QSBs>shnb17kEt)) z*VxC`gl%bR;pgYiHV?4$WBIfFOpMG;4Z_3N!HWZa8g^9wJN^5QrB0KgIQitPf$4TfY`B?avu`PWpEzFI4S-$2be&)Uw<}6D?Gao-g zwhs$$S|~)*_M5kO!NRa5K&`+*I}tWYwTtmf+h;K4aH+6c)=Wb)ym33Pu9A_Vk&!>k z(8$oj0*rv=Z)#!0@;9{bvGivf8~O#{Dw8QRyyByfr~+YgLe{6HhKW=e&y@=IPm`;q z4ZL_^kZudSu||9}J`~vp%=wJS`x-SnpI^-PJGi~bDkF(2_D|jkVaajBD2($h_Dfbt z>dom&^;m7ozW(c&IG+lFFC8CcC-EU-jRHPlnd{e>IC&r@PO1Rqh4~CIFZGiT(lR8~ zygRS#`e`_vLWjWK*Zb0IOb@3vzx=3w;7`Kg(EAj8oMp=6Xj-2RzT4$3jZ3J1jR}eU zIvmcsGtWbM=iYsP+0>ZPFCWr|J_;KOhl7jBN$#YyuNm$etbNQsTwsJ}T3~e?4AU|i zKeZWrM*KDM$gTHf?e`+_LEZY2*?4 z==TO8s#|+Lu1;J1wheDVD*rDdk2rKlSncshbKV%>pcB_zR*5?JuOp9?s5Ko_YP@D+ z>mD|0voSjnx#Ksn9lk%6%N>H@n?tyq5C6x$^jp;!(?O9@x@xo~OKS{w2!^jMehj?< zqjG$mQBLrQh?YrazK7O4yvusKjQ`HC@*hKQ{MhWr&>P>?8T>%*Q;XsGW>$o~^UFzI zL9(k4{1|%UyJkOz-hjp51)#8XaSNzq;$Pv76?l zc;y876vLfTuepMNz@Q;8u<#@-mafk4@-XOnX zg6$bzlgEJ?D$5&;1HVRQ$eTAxa*NO(1b=?iSS)q1@WHYZ_P$~lw%)e>#}fxXIlA)u zZ?Okm)Z|Vm zB9b)@B3buRt{fOslVY%N$B8qyUDWFD2jk$YZt!Ypg5-3P7fu?@w^HKA&w0B#MRoE( ztm}(^u{Hiaf;(qxOxc%E8`D)2F_nFVHFpYYtSzpxl%k6t?U0z1=glWIM+%?fE~KMm z@dx{3?O*PXDH#vWl=YAEFbAGRCnh8YIjVrdOOYSq55g}xvw+l(3cel z>T0i{H_QhJi*cVe|6bx`LalRAFUsP2sT~bhhH*E*cB7eLsmixLb zrg*Fq8sRv}?c%)$weKS@^{x8tSJ@VnnIvAk@+iNKqK_e)ZRGC*&NP?>82K2P`mucc ze0|MLeSHH0Ow94omJ%`y-;kh1^A-+0nh@J?%j&bq5_jdIm)-JoU3bRiY{Hu5_4`jIovqHu;NW4@1HBD{uAXWI%P{T=;x+S6z&)k+n*@s@t1bh{i@p! zcBRa^vv}%m)jRHT4N`MgKY!Mc*AOA8G8FnfuqVf$yTT)3*76Nzix%0_Q0R9YgnsX! z=Z@#lfJ#%oj|txfm_$tYc1WR^@Q6Y&;Sq&m!oPu~C?-6jP)zs^$OAFq z)o9$|AzDC5H>(!kZc9;?;f@7C6lQ|^dbA`JeC&$urXZI)SL}yYB;ykOYjpWYo*%+L z5S3d0A=dj)mTvC&58bGbKMge>`9si$9@PFn4mD3S|Km{ek>AIf??s)XQ1g4Lm%1Fb zI^j)G=HYpL>D+wqa)y$#{GsU9fyl79&IZ4iP{X^&p$B&a>T;bA=7#kBM@KesSF;RJQ z0|$YGZ{{`JmzCx{r$&ts%iC6^-6@pUNK*~x%kym3jw9h(3aXU6xpx?S{tVi^y@AV? z8~Ymv&Yc^~_FuFpAjp4--{Qa}LCfcHvOw`_#mrGu1wS?LQ_q{D$XKzRWQ}i+6C8mn zJmYiC+jd=hK!Vbfd`)Vjh=sevhVe6Eb3OR-1}Lf)e3ZIIRmHjE$5kw^Nt>>aKBF_Q za*Ln~AHJPM9f4QSRH@2rPWE#Bh@gdh6xkc&WhwaCnJ#yyv3x`QUw}_oU@ft4Ydyxh z`uQf{U_`?-2l?K_C%c4@**h!89)23y;(V`P|K6cHPT+G_C`6pAGG4h!aB`mY03V3A zta497dC3iR#c4B2_lOx(YT((jsvVS1_1ne$lU>3!t}J;t|GDTZ{1jbvDP_jYmZ<8j z&AY>ccxpt>T9oemOjaUQ<0`cj2ZyCF=$iLXN51SZKSHOdpnO(3^X*ydL#{3P{a>R28J6TeNkF(wr7;J%XB2!QiwIZ5@WgHOLQPX+7jUhm_!RK%suqD}lVbkmvban@MP}1yU~|?>wYh zA=QWra(Ij0$ubFfIrkV$qK`!{fIQS<52UJL3+4Bq-#d_c19^y&c{x}w=RRnQS}cM* zRJsRJNEhV~plu~;`%>GOkQeGPf+WLe9mEnpyguazkVAQD3xYwXhWS#82f*f8Wb40|$nGi+fjl(o$BZ|obKl#`H$CaR`gTZ<^2(=W@J z_=J90J)*P-QmC_pR3I6duJ8?Li&S|9DWvkE&p`4EsGfBP;Gv-fLJBz?sSYUm0G3{Y zJXEi7Kahu{P&xq*Nggpo6%GJhkf}NcfMlfoWmt-MFCc~FMLzA-zp+nvgEj-}pofu^XM%v(Sq1#%t(AxP1MkcY%292hiY;ahM$AoV(7DQZja z1{5-G8VN{7z8Va8!ca;#>!mLElrU$EniQ^F&!7_4xCNUat)}pTCTJW=J%EQMZ~~-I zDVqi^PGq5dkcZSNIs}wKv(*K8s8p^OT-fN+9er3(lNd-S_!Hs zg~B^fdJkG{Jfx5YY*|H3LW5MuLj%6~4)lhkG|2*&hmyNlz0l<=9TYh(Kj}Sid88Zr zy|%G1oOyu13tKaM2iXmy1w=FtgL(eS3lw-96gth=$z$DOvEO5FJAR} z7AZ#E_g<~vy7PBAeAOU zcP5uY3jeGy za~xsYauKF8p{SB5)0I$#PM<=I?4~GafF_C}Hb~7FqEaDs1&Xtk2VEdacR1h`Lf&1- zL#Dj|d59v30}4&rcu1iMiiZ@MplWfT3gT%~26>Ahg`6M{QfOKRAaxV=k*-7^N3=+g zVA>H@u9g5(N(@q&K9QKU3T2=Ra@&iNpe>?8KLg_;N1Gr8c*u|mkV0!{R^%6unybtvMxV zM6QKA7bCg>`8DLFt123h+aM*I4N9NE(#hF^Cgdd8ovoUp3Hb=*U530juwIUuqAB?U zZ>d|RIGr}rUG?~Wa z=aA=gM$nKLPShDeYogNuO{SiVYlyO<&GZxLQ{HF0KF;0ihl9SmS&u_;qlt>Tc!(C0 zLLPtuLMJNPk{`n{-L4k2B{x7`!dXEx!8?HJX05l!E1P+h@Q{ZE_^aB>M!+9vD^6hWEzQoQd_pYFo7`DVV zAd7Y?l1}|z#5a$097$nMsRRD27p6qNO_-a)){^sv69B@(so*ZoqB?jZ6S!SF)P7a1 z;7Bs;2ftV=$dCzR-`hHdv5FEcFLQtV%LX4f_27F7kkELtq7nH8Y@t^ts3o&(>d7uq z>pNSm>Yngzx)blvjp}Xkz=P@pH9Ys0RXiO2cwwwb8a}gMCgpGo8b_QI=_qF$KY5b# z9((}gj37vKMc$kdyFR~>ja)U(b$Rx7H0uef`0+a6?AcJ`#yUY$@)kH7vX20j1$iVH z4O8+NNX0_lNl39r(rpEw0}2>G1MlkK)x$?T22OAS7P>qZG!=y6z~?!maGNgRAz836 z7Z!%f0%?~a)d#APA3&~fHN-0byc$U5SkpO_ixG1axe4;NL#i0nGXheOQ&Sp&qP>6zzDG93_xAxs z7d8S#QK69nQ1l^ea1m4^KSe@D1HsVrxS=sn4>rYvn?NbLGE|I#U}zM)u}RRGP%5Vo zP^Ta-+JxyWTnZ>~415^rJl;99MR#kx;HMbvTRQ$Ytp(;Rn28*)6ffygt^ zNd~OfWDjhGW@9zvU4gtDGoTEjIw0>H0+P4twJK$Q|$FCOx$AeA#4sDdsl z(kn1++mQ3tu#07c`V;Pi4}GSqa6FKB=an*Ig|IS+-@K?(`kep_%HITp5< z-2r%Lsw8Yd3v_W*Kpwh53UvbNK5W~i3v7!fcO~Rqg1jj%z{+Sq?T}Xlc`Eimc_h!f z3oN)d01w1O_+fg!^8LP^gkYiBbk7=n5s7Xq_&teHC6Ca%S%&J z-|-0FpfpTJk?rg2W6Wat8=3g}nXtget3E8Yk1?BV8enYb&&H$YNyrEdoq5BfXQ|L| z)mZ|6+pcLiLymIY!i2KI_|>~(sqJEV#o4^Lgt4qOo^XXcI&c3M_4HJdTApnxV=dtC z22_(@Bx{y-L|{W-50px6+C_&bIYcH&a%4z zA$a!`SGmiOvK|mg+3K-HQ@Zt8ZeZLwiCayr63j!XsMlNX zRd=hiH)LkW(mG#-cc3PGu}lq|68JjsjLXFogV%5SG157;8YJW6H!>D;WPENj9=Q56 z$MuVq)1mf#JiINaX3IAMuI%;Hl~rKPD(qc)&#KIqVI*sFugDt7cO!TKmQO z<+MA@u(S_&2ur|=YXuC`2`!BbzQ!DD(>_7?`1vrX2M%6jQv;8xCaG+%vuN+oKB-`T zCNi7Qu>WhnK65$!);MV1TK}R}d-?iBo&4(!=k+2DJHM$J%&94IYPGpWvHucYTV36J zoy7E)us85xoNB$VKYpLN&BsrdTQ*)8>0?_=*tGYn3|}@!hPY9PzJ-yR7XI`ug=f9- z1~f|WBAtp^_)4g4(W_XUwfLe?*wd=lNIH12PPLxh>TP&OVbi|Qw5wTVTT^Aj?TU}$!OFd76yZg<=MEj?yGm-X0JH=w(CXhQ_rC8;Ke*0 z%irqKZuX|{{?)NvZB;D(A~-`hcu`NcYk7J|M#p1MhLeqc++A6H{vOnClsGAg<0<~Z zdST=2IDPNgD07@z;GeQOs_$b^2Ypc&lmlMw)3LQRk(TF=J21S|ibhHcxo?4TaPYF9 zj@c^j8@uW=+ss)shZO5_-3pBvy!@wQz02q;F07n=CvH;PMv=%#lE_W~44_&E&cil~ zrya2`UnVkM#=Xbjhxzgk=V%mIy8P3*P#w4Bi8F7%jiHWv0s95u0Zr;rzgW}cji*~Y znL($LOPZg;0)Qn@t<~ckQ}b$#wyi*k9spGLGOA8FOC3kq8VKr*Sb5TP0Pwx zFP^_g`udF7pP?E!z#iz>I?{E1XPMT-2T^kKAKp6#z{`Tuc&RxinzKvgU7G>W`N{gQQA|C%p3Yso}T~IO2lgCdP+kA0M7;|a5-NcKz zXu96K)K_dlPL`o3jXj)(kkH-iXEG+cmfs9{$=zb{KC2@C8`{j9~jNmfG|0jxs} z+ohkrRXt8I*0th=__Rce<>=A^*oPYZfXDiAPgN4U42RjR3|pQ_hZ=BWA(-o)$A;+w zxouyJ*1dbaLH{J0SpbVDVzwE)bT6T(J#Twv8GU;2&KxNNU=lg(=;afN#~gFlvZ{_6 z4?N3!iAn%$LbV>1A{wdLR<%9y4tdGJQ*G zZ`G}f{06`*R7~QMp60|0sWiv(I@0*{fvRX~0qjDx7V}gINGUFF-aDXIrM9^DEU^;3 z_$qzg;B~ISSVuXtda2yQ`l_&OhloZb1Hd#??D;jdBR3Zao!(P5<(k$GBZo#f=>WE& zT2p;@=A2wUJZZP}vS6A>r0!#G-RCXi=&t@DxqfNYXOWIW>Lf}N{Jgatd0_;y3n`F+k?sL&y zI1m9Z*KXEpUD9^WQn8R7?8h;{ybo)`Jx3ZIwv$nOcwA@63M3x@Q}WpRvkx32R(|v% zdCxz`O4GW4N&rx$fR*#x=B2uN_CNCJvOAPK`UP4J0C1&%9dR#hUm!(3|3Z-`CT3E@ ze54ZqvZz?W$2(Rp4~DI8n;ymrB;WT#UJrmRs`dP|ddw^6#N8Du4U3)#jd)5-R)p`O02l+V zaD~?6`X=E}*vrNz>Jw5XL5;xe_p7CY1}(jOnogl2cKz*<`Bh~CvnOgG4FQlwwcfJK zNlkHH{snSqjIi>`{Wn?%?K!v(1r6R{OV0|1<|p2Mv~O8VZQcFLb@1kZ1K^E{eQ~Ur zfR7m!;CMTE!eOBcPLE&-0OF|DyrLtW%Z?o8jqDlYn3&Q(lUUdvebq2{@P_hcc3q|o zzvJ{8VX-?Od^I%C1rC5ad8}Han>=x05dE^CO>p%U%JK#%9RPV$%sBV9&4SxP8YAlc zSHw=9co*H20kB84Hgt$ldNR63@8GCi4*R7K-XbpW*02388gy{=XL5^{+)y|+?u&Sr znDMNKaQrv`0;$-x?A7;frtFLHP5kshqUuv=6)XY3pp4!7k>uWLg|hWzhOAHXjC=)R z#meFzx?L>_*9)_A<;?8eou&CHh;UL}iqD@t7YkhxSr7Oh+WY@}LO8MQiI{xGv)Va^7|YM)KcMAx6b z4lHa5r`xs97SG*2#=Puh+BWGt<2wmxQUEwfwJz~q_2@}Vx_*O@!^b(5e0p%V)y?{5 zU$%GwN5-sP9_>#8kD6{7YRwoquW~jr0RS_pn67#I^*u8`_PQN7C*N9{T#7D#0B*`- ztqR6=Zhh19F9esK?mmuZdcAYsS@eX@LB<4NDV6?q@|S&{%Xn(~ zR?yc?JC)o^jOsh^beJ_;FH2iJ`;wQ=$~(NbLjp;}6?2F(bUlU-`o)Ni!G)QEo+B+D zg}AlMFO5eX0kD;dt#o{Jc(2!y$?Gm8bk7ae4?{~c0AH!rX1l*Md-oapMl|oruPAj9 zCzjQQuLBGpyz6>7%@S-orzC5+;Z^_S@OdrhS^{7#)tZmJJ6vx3+g0BAb4^SaB+=m2 z-p%@E^;|MIIk9gAT|>s|OmXX6GXKHzFRt&Qe*$oqimhF9|4{jA!?gt;ij@zTp07vl z1HfLYjs0QiMCO=*^y^F1CLSA;kV8lZo8$z}>bca&j5GK9nkhFa9UtW~C65GU6uN1l z+dn{uX;_N!xyt$#?W2_BvE=XKUM32%Kq>o7E0H!Fkx zt9TZOm4tRRE9kTLXt3Q2ZyKy$&?4La2Cj@afR}058yQiXK&dTzy>>{EUP@fmKsQAI zH`6fdIkyD-x9u@gEE(>w?8XUgq!NIiX*N^OPA+aeq?aQp@_J@r$4DRImYe@=$EKW) zKh7wA5}-ZLev=t~_Qi8IVPqVDSA%h-&(Uukp;L5t|NW(=l}-t0QUHEUvsV6ayEC`3 zIsF9RCGYy?FP+3Pc2#W1ZI%vh)|wm&98^ZCy+7G>WI9#kSsStqz`NDx?P*yz4_Eq5 z)iCfI=XYi1F~T zsgi`}eg};Y!^jaw_$yR2ta`^j-Qrnpw*oDQ04i6*NYj#Y9Np|_TV@|Rb=xC69Nqu` zbgqCEwmyv*v#%&%!RM*<5h+j7kxc=Vu7Jhcn2DS@v}%A(yXM$u@-hk;3P9@$c2`GB z)HS$2U9qEnx4Z3%%9U`C-K^MQL+js)K)o>g$sB{*=cH7oVH51Ny|QKIcwHS`jb=I% ztbfOSA7}^4oOu}?SSU>ME6YS{FM#Lim{4;Y>$2sR%Ex*B&#G(}8X!9WxSks66koG6 z*Yw=G{lj9%xkcdxtSl9Za1m$zU!2ZRIV+iteK+I+sCK{5N*)@sX> zy01_<2a%(KfbA~S7Z2Mf9)5mpnWg2M41u?x9Kv(Dn380p^Q`(KxdsI$Jn1g{1}|U< zfa{bod-)g2KCbjtRw>a>uAIlB2B5Y8zGKnvhFDHt(=tiYEGv6|i0dcoHq_e?oCo^L z@gb=g+--h)YHIgO|LHY+nXoOu_LQ(UWfOB__OI+Yq`hHThuuScI1-5UsbYm?a}IoX z#V-~rLpyOR=uG(**al#K8rTk{oof!>-5+Y1bjD06>L5BEHbMybfw0V zRnr#C`26f6lm@UtCF~eII9BgHyDHdiOV`Ol6U{`}24ICsn0m_bcnK@|?9x+v)feGk@L)xu#LfF-J9BGCvdp7bZbijT9_Q=4F z6gZ{iJiijESlg_3e)u)9U9b(nB9$-?#=&*vD-QA9a*P$p)$)8F4@&?xsf}qEMe4SC z>Imo9_>Y>~I_dp;C=Fnhy4arXX>QAGgd-jFW=%XScRCJ@2Vj>>tfA7v#eG@C;of{l zT6N}@WglQ0fMx1pPL}hJZO*+lcJT$nP<;HvfP=6ffNd&aQ$sH(g)LQmrY?1kO%XW4 zM6&>}PF2h_=-%7$?xt~yTSQJ(>DTN}fo%ZzsfziD_^Y}f^2!fp-J8JsbY2Cr8vsK! zWuhc)%O8@`w+O#Y+O=Iro&t3n1V@3Jw8*dCIb2A*=9z9mkKx4!nP_4GQmTs082i>K zcfRWA^vAIm>iySu?uDHJG`BRunj<3m9dARU)b-c#rH|nW_tnc z*(Jyw0rIMkUDZnRFj(TSPVT)&`=*`y_V0#m5E475?M8aXQfX^k-T)Kzax zBQy${>-Qe*RnCk?eF4x`Lnde5r`pg<6GyZ^p>EIE<9``C#1M!JEQn2{uc~fMvxvN) zuT!*Te>vn8sxRBc2B8qrTP_XNx9%Om~(bdG-M_$uEUv^XI!1?hG=`y*r z3^+P~@akgo?ug%-Ry}!w`J>Hcm)r$NUtozXELj?TKX@2Vk%!`}v+geXqm%Z*l4w}6 zwd9QK^`2#uYWVmo4_(Q1L}Lbsu%=9my4T6Y@26Z9<3IS|uKRS^FG!1T5MoN?=k>4E zatfXZY&thGk$Qim6kIC+5cM~R@u9Hc)GW`?{N}bh<$j)d!E2F-KSPW)mMjea27F$v zb=6d z_$zo>U3u4h-r975wIjnW^oHJ4gzFkeH-_M46-@1x)suM}bVQ1zHg?x`F=^SL2M9Ma zu=Jbq1s|&eaz_l`Bst(X5ZD9SLHL=0@lpB6?qn>x6R?pumr%>Mlzb3ty5gwHmxx@ ztC^)&c4RE!#2ok<%*GXO>xa{I?wj)+s50ewggx+(xrCtF+F9*e){K z`>^mfC>g@GLCam%pZe!xORe+1Jf_z?H_N;PTLPe)flV5-d%{!!1?`X>NxCu-J5FAK zB>?zlU_zUBYqvbTG4r(8wOKP)#)YEw6ae82Y>gt1f0gvziz>_9uT9*+T2uks0AQSf zm8&fNGA(_r$-}*_r*`_Sj4g*H04Qf*3MCOCkCT{`m@1{c>nG_v`3P+YfO7`+Y3J_S zOQMrv9T#ZMc#-a-R|newAf16F^U?Q@-)VPv#X?clMH|;jw!;zttm|P1)HNt)d*VNN z7hGBs7Et)S5taa;T@S1Fjf*~gcYGzLFF0LJLCL8MwJ88?KE%=ln)_WsYCO-q=;6!a zf!2jUydI`@&Y_Hd%-gU;`$YcR(~hh}y8-~r>tWLaUQ8gBIzH-l47H=^cITnx7XbBo z7^O^e`OM{#MYs1hEyzE%nREm7V+6DwtKO)7y!%j`lxS}4N2i-|PhbfE@)(%N^G z&t+U|jh{@}y-URpF7rdcKA0u*%-LZVg;nmJP>XgNmztUV2DSt!KLaz1GoB_G&bP}c zC_>T7d1Nu%gFw`uf!#|^eD>wNzV+61wJ)#b=QMV~lI^3xs1{sY@v-=k)Pl?dXH3m( zgZjH+2{;wNz|`(6nIWjoduadLas#=bJ?G#80#6Bmd$nY2c)w)L@C{8)g~fimPQ#rU z4o(d)uq8?^%?IWd1s~pSQhV#61wXn|f>Q)Gn9pI)k`%{J4>uOdn9JAhv@M4HfKvrF zm|~=0!loIX0i|yI_@~Spq|>kjoHDSXPte}8HdA0{)ZY3#nVTJMrV#%6-KhgLEYUnb z>fJ^b+2^YIvqYz20kp(`QwTO#(1yl&P5aB_?T5e6z7zW9G%_(bm0*hT_|uZCWqmeI zt36RV61HsBNk1DmaQm(+L)1bRQVqH9>&lbHpu1e|JMU=IaZ6P|>TCYroWR4X&HG``>?tMwgIOg7})Y6 zx$_go?SGBUHF}?T(ZrDq7ymw6Pd# zF1CO6PRC4gLIM0wJ*G%rb=AcPz9xdQxps=-)3Xy88z!$>=d&4 z<*sb`7FYsKRWLC9(YB71-WwAx=WK4|cc@Id21~#x3kFs@c}q)I&0hQBiDp7yj>R}_ zgf3xZ0kpo!l-@V1!ALcB&BWt#BV5-#g>Aqo33b_FF(1IN4%g7S-=J!plxfg>dhnJ4V^&XxP9{(Eyu-Pli7-|7Q40(k|ABPL+H z{I)0Lw|0}x@jSIN>OSbS{uyiu&YLi>&=C8nYk6$%J{b5&oqcSU7h250`4a}F%UkE2 zka4=)*H7`4)%xVtU&5Yk?wZ zqoj=5$mOq5Kj6Fy1CyNo!pO`?$h4$lPWXnTsMpV-G;n@}f$?tf3iV>AOf#KT-0IX& z(up1&zlcrmL(B0_NlGopChr9)xZ_&mk=5=dao^$!r^63txZ(d$ifa8Vd zUG%W?7nboyld81@6szvy9K9>s;5T^{-JgUTZ3-F{?ubgKjBVHnsB zY|D%($A=+a3|2KC3lvzL5Tc<^r6TVPG!77Pr6b z;5U2!V9Bt8O095o%LV6W7+7%2mx;%)cN<*Psm?8SBMp!S;5-dO1{dBm?>=+;x`ne8 zV%P3*Jzfp^89HABW=X-XTyai=*#gn%k;y#G^pZ37j^GN&a zKFVmB2ZwJMSmNfb#c~=E8Q2C}>Co*e7tr+y4&wmbO`XmStJ_#p;nO(wPWGjAwETj@ zISh;(w7<5a*vkCa=^1^M?Bly0!102^I_6mOuEx{X$4C^Mzcr97cfR{|FVp}W-eF+# zd|njab3JJIYNylVSsvHB(8>x9^Dr=$^0V|w6(vcd&hL!GO#PRAfNj9x9tPHtYM%LK z*|N0D%Uqayjl^TnV;nf_!@%~%j8K%X9Cz^I(eaTEOOI$hf*OFsKMX9-t35IB+Dz|^ z0y}2Z#kQHqPrzXy2KI_qb?VdbkT>^>suZ4L_r9Pu;BXKF+vDXm{mrKt>DE)^njPqh z;h&%e;II$_>r-{O5vj3tPJT6u!j~CfkFFSSc!+_$_cML_JR?Xm{*wO`z1i3W^dJci z6KTps&o~(_Gwh1gvIi3q#INz$LyHa_E&{V86<2pec(+U6#+AZO3mjbE=R)1VNh3{+ z^u&hoDf;Qck?oa26P!2Xc0)bD$sWp9&LtnPBOWdbLaG_hMg9?Z`Zoc3IM zvqXw9CEx_QeS(uqnpjarK^sff@m9DfCf@RDJRQv+ILV}m3BO%yvhgkd-Us8vUuG30 zd7%3RIN79$>A%`Kuj=J6r^S6oBHSf+CRf7ofRj!PY|;IB)|pg^B|gFP+Zr=6kE87X zIQe9QMY+D2Hoi-Ud3u2b$##XqLUbh^BOC% zgo0$JY+Sekt(@RIlnwsQ5uDdW6~Un>V|>&^@E1v#G7+4SLf0WU6=jUyf(7U{4~|BS zBJYQW1*f7$kxOAG;6T(UGP)$dSttg6!wFPITS9OkN-0v+uBv4I#24)cuF^-I=N*6h zEocm`y3GotWN==@`}Y1|N9vI?_>18kJ0}=M*sU5 z4er_hK1Toh82#^K^uLeM|2{^8$EANCqyK%3{{Q?j+W41`(cqalbJhBU@mX6prMqTH QZMn2%8vM>kc1!I41FghP;{X5v delta 41449 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 VBb#&}4H3d7qF{am>ld#r{U5dq^F;sv diff --git a/android/.gradle/8.5/executionHistory/executionHistory.lock b/android/.gradle/8.5/executionHistory/executionHistory.lock index 21a4f51f184d80820cfd1de7fc5d2a8ae13b5108..1f3aa40d61e96db0b82b6ae2f7146d75c834cb2f 100644 GIT binary patch literal 17 VcmZP;6!{{0F8VhY0~l~^0{|&(1Rwwa literal 17 VcmZP;6!{{0F8VhY0~jzb1^_8b1O@;A diff --git a/android/.gradle/8.5/fileHashes/fileHashes.bin b/android/.gradle/8.5/fileHashes/fileHashes.bin index ffa5ed69f3ab446ed10f5a7be8ab1bd382c69b55..bfa6bb8586b34fbdad7ed20a8e5c48d6b3c81986 100644 GIT binary patch delta 46312 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 HgdP4LZe<8s delta 30795 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 diff --git a/android/.gradle/8.5/fileHashes/fileHashes.lock b/android/.gradle/8.5/fileHashes/fileHashes.lock index 45d5cde785284aa5de96d0b6ced1eeae644b6863..ccb5e0ebaf68d979de9e4f6a32e824c7290e12f9 100644 GIT binary patch literal 17 VcmZP$IPrPwk+NI~1~51!4FEiX1!w>O literal 17 VcmZP$IPrPwk+NI~1~BmO1OPj%1rz`P diff --git a/android/.gradle/8.5/fileHashes/resourceHashesCache.bin b/android/.gradle/8.5/fileHashes/resourceHashesCache.bin index e8869edc749b570822de3789f1486844ac18a169..e2f2aab81ca8a30bc06a2bffe7cf5f2629a70c25 100644 GIT binary patch delta 20665 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 W?@hO6O*&{z{;{)BkL7Je@%SG`XWAeD delta 846 zcmXYuTWFML5QcZ95d?`4kbl0!#H=wJ6SMn&e{^%$m^3kF_uqo_$^`|!`{SqQ=gp=kfiDKbgvCD#L zX^F=-CBk1yc!SH28+;dPFCm08Bh422tov~qHz%RALbx#5d);c~f4LCCT@ zWatYyENkAf$r7m$j#L;fS2!$GSPpLXsjTc_pwgi;EcAvm6jXT}suKFDlDttpvBfa3 zMc=EnR#~X_D67$)HHPq3_1=1v{k6hit;gz!LoDL+CgRZ=b?A;7E<|;V(zX^l#g;gtu$$cCA(2AG7qw3@b60SiR43eTsC0`Zic* z8hjFsx;JI9G0E;G;g~X}bjN+(#dRmmhI7q6E6o`W?XcX~(Z!w?pSvw7qOG#-?DU>9 zftBz6yUAxIhHC*h(J95U1m2?vI{$PanM zhkb4jTZ%@6og)sXMhvS)9ePF$*GIc(8Z+D;bEtl-{gqRXv($}Sz8ZJ9HZJ6qrYFMT zCx-K?PnDvH45ud??oC)yPlc?qX3}zaQn;nOoix-;3Hzp!&xHO~|4zY&O+nIamII5zK%4Np z=9`wfm{`&yw(IcEg`8sYDX!-&;SPrr9U1oiZ1H~SVR4rpv(xhJvQJ;9&*v|E(g_Fi p(&x8?us&%>Bwb$ps)org*Z%W=1OlPltx#Jby{`-JeC!Xq?*VydR-^y` diff --git a/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock index d350e3d0df590e476217f02f8ae216250c8ba116..d603f86a578bdd2670b7054258aae4a08e87a1af 100644 GIT binary patch literal 17 VcmZQ>RNa4KJ@aiH1~AYn0RS>k1c(3t literal 17 VcmZQ>RNa4KJ@aiH1~8Cq0RS>a1d9Lw diff --git a/android/.gradle/buildOutputCleanup/outputFiles.bin b/android/.gradle/buildOutputCleanup/outputFiles.bin index 1e5dc95af17e707a6282e0f91bbb07a705722348..d05cd6ff6ad745155058626eae0ed9aef5fd5492 100644 GIT binary patch delta 8943 zcmX|`d0b6f8^>FF?^B(Ivre4`U7}-XKq(|~lQM?rNTc_v5RR$3t;x0 zNGZxGOI#94iHKWdx@E4YM3PkRI<1}aPd?xM>}Rd@JkR>AXKhg(lK;*bWRLFSQn{6j z4Njf8^O~CKC(w55=6_ptlfXR#@8dlj>{|7c_hqk4c&(49y|_a;rH>wf_{>eW%|k;d z)GJiDn?%8~b>S9=ZNF2dVXr7Mk;1ys=f2w=8%-HBR6aM6v9K=VmxkwC&QZu#Du`=!jCr)z-=xBYGW|qlc>g*8UEQ=!*Kd1Ip|O+F%Pfb5=aZMC zvq{G&`BK&r7LmUd_O&n&WqK9rokA@uh3@cDs4LTG9$eqMoKnTz;W9XCS%<;gj5X9w?58xeCabx4 zf1!9~yo5rvM1`r1BP_Y5^gPyw#)s>kq;$9GBe0BVzd0TZ(78%2nOnVKkLAi&TwEPG zyoYn$OBKJ3DurwHA|=i?�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 diff --git a/android/.gradle/config.properties b/android/.gradle/config.properties new file mode 100644 index 0000000..428649b --- /dev/null +++ b/android/.gradle/config.properties @@ -0,0 +1,2 @@ +#Thu Jun 05 00:28:15 CST 2025 +java.home=/Applications/Android Studio.app/Contents/jbr/Contents/Home diff --git a/android/.gradle/file-system.probe b/android/.gradle/file-system.probe index 91c6f0e3b5094dc7e39780cf8842f29932e86032..74e41fcf6e3cc8c2ac71dd097ec90061a958054a 100644 GIT binary patch literal 8 PcmZQzV4QBdY05nS2oVDM literal 8 PcmZQzV4QAsWkVAH2uA|h diff --git a/android/app/build.gradle b/android/app/build.gradle index 0595b7f..556abdd 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -51,6 +51,10 @@ dependencies { implementation 'org.tensorflow:tensorflow-lite-gpu:2.5.0' implementation 'org.tensorflow:tensorflow-lite-support:0.3.0' + // Glide 依赖 + implementation 'com.github.bumptech.glide:glide:4.12.0' + annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0' + // Room 依赖 def room_version = "2.4.3" implementation "androidx.room:room-runtime:$room_version" diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 4727907..d3f87af 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ package="org.tensorflow.lite.examples.poseestimation"> + @@ -39,6 +40,18 @@ android:exported="false" /> + + + + + + \ No newline at end of file diff --git a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/AgeSelectionActivity.kt b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/AgeSelectionActivity.kt index 6dda173..b3ccc61 100644 --- a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/AgeSelectionActivity.kt +++ b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/AgeSelectionActivity.kt @@ -18,6 +18,7 @@ class AgeSelectionActivity : AppCompatActivity() { private lateinit var nextButton: MaterialButton private lateinit var backButton: ImageButton private var selectedGender: String? = null + private var username: String? = null private var currentAge = 25 private val minAge = 12 @@ -31,6 +32,7 @@ class AgeSelectionActivity : AppCompatActivity() { setContentView(R.layout.activity_age_selection) selectedGender = intent.getStringExtra("selected_gender") + username = intent.getStringExtra("username") selectedAgeText = findViewById(R.id.selectedAgeText) age1Above = findViewById(R.id.age1Above) @@ -82,6 +84,7 @@ class AgeSelectionActivity : AppCompatActivity() { val intent = Intent(this, WeightSelectionActivity::class.java) intent.putExtra("selected_gender", selectedGender) intent.putExtra("selected_age", currentAge) + intent.putExtra("username", username) startActivity(intent) finish() } diff --git a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/DataFragment.kt b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/DataFragment.kt index 250c609..70e7ff1 100644 --- a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/DataFragment.kt +++ b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/DataFragment.kt @@ -1,17 +1,77 @@ package org.tensorflow.lite.examples.poseestimation +import android.content.Context import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.Toast import androidx.fragment.app.Fragment -import org.tensorflow.lite.examples.poseestimation.R +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.tensorflow.lite.examples.poseestimation.data.AppDatabase class DataFragment : Fragment() { + + private lateinit var recyclerView: RecyclerView + private lateinit var adapter: VideoAnalysisAdapter + private var currentUsername: String? = null + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { + android.util.Log.d("DataFragment", "onCreateView called") return inflater.inflate(R.layout.fragment_data, container, false) } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + android.util.Log.d("DataFragment", "onViewCreated called") + + // 获取当前登录用户名 + val prefs = requireContext().getSharedPreferences("user_prefs", Context.MODE_PRIVATE) + currentUsername = prefs.getString("username", null)?.trim() + android.util.Log.d("DataFragment", "Current Username: $currentUsername") + + if (currentUsername == null) { + Toast.makeText(requireContext(), "未登录,无法查看数据", Toast.LENGTH_SHORT).show() + android.util.Log.w("DataFragment", "Current username is null, cannot load data.") + return + } + + recyclerView = view.findViewById(R.id.recycler_view_video_analysis_results) + recyclerView.layoutManager = LinearLayoutManager(context) + + loadVideoAnalysisResults() + } + + private fun loadVideoAnalysisResults() { + android.util.Log.d("DataFragment", "loadVideoAnalysisResults called for username: $currentUsername") + currentUsername?.let { username -> + lifecycleScope.launch { + val db = AppDatabase.getDatabase(requireContext()) + val results = withContext(Dispatchers.IO) { + db.videoAnalysisResultDao().getVideoAnalysisResultsByUsername(username).firstOrNull() + } + + withContext(Dispatchers.Main) { + if (results != null && results.isNotEmpty()) { + android.util.Log.d("DataFragment", "Loaded ${results.size} video analysis results.") + adapter = VideoAnalysisAdapter(results) + recyclerView.adapter = adapter + } else { + // 没有数据,可以显示一个提示 + Toast.makeText(requireContext(), "暂无训练记录", Toast.LENGTH_SHORT).show() + android.util.Log.d("DataFragment", "No video analysis results found for username: $username") + } + } + } + } + } } \ No newline at end of file diff --git a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/EditProfileActivity.kt b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/EditProfileActivity.kt index f36257a..77a87b7 100644 --- a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/EditProfileActivity.kt +++ b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/EditProfileActivity.kt @@ -3,6 +3,7 @@ package org.tensorflow.lite.examples.poseestimation import android.app.Activity import android.content.Context import android.content.Intent +import android.content.pm.PackageManager import android.net.Uri import android.os.Bundle import android.provider.MediaStore @@ -11,12 +12,15 @@ import android.widget.ImageView import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.ContextCompat import androidx.lifecycle.lifecycleScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.tensorflow.lite.examples.poseestimation.data.AppDatabase +import org.tensorflow.lite.examples.poseestimation.data.UserProfile class EditProfileActivity : AppCompatActivity() { @@ -27,8 +31,24 @@ class EditProfileActivity : AppCompatActivity() { private lateinit var btnSave: com.google.android.material.button.MaterialButton private lateinit var btnBack: ImageView + // 新增的个人信息输入框 + private lateinit var editGender: EditText + private lateinit var editAge: EditText + private lateinit var editWeight: EditText + private lateinit var editHeight: EditText + private var currentUsername: String? = null - private var selectedImageUri: Uri? = null + private var selectedImageUri: Uri? = null // 存储复制到内部存储后的URI + + // 用于请求READ_EXTERNAL_STORAGE权限 + private val requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { + isGranted: Boolean -> + if (isGranted) { + openGallery() + } else { + Toast.makeText(this, "需要读取存储权限才能选择头像", Toast.LENGTH_SHORT).show() + } + } // 用于启动相册选择图片并获取结果 private val selectPhotoLauncher = @@ -38,9 +58,15 @@ class EditProfileActivity : AppCompatActivity() { imageUri?.let { // 在ImageView中显示选中的图片 imageAvatar.setImageURI(it) - selectedImageUri = it - // TODO: 在这里添加保存头像图片的逻辑,例如保存URI或将图片复制到内部存储 - // TODO: 需要修改User或UserProfile表结构来存储头像路径 + + // 将图片复制到内部存储 + lifecycleScope.launch(Dispatchers.IO) { + val internalUri = copyImageToInternalStorage(it) + withContext(Dispatchers.Main) { + selectedImageUri = internalUri + android.util.Log.d("EditProfileActivity", "Image copied to internal storage: $internalUri") + } + } } } } @@ -68,12 +94,18 @@ class EditProfileActivity : AppCompatActivity() { btnSave = findViewById(R.id.btn_save) btnBack = findViewById(R.id.btn_back) + // 绑定新增的个人信息输入框 + editGender = findViewById(R.id.edit_gender) + editAge = findViewById(R.id.edit_age) + editWeight = findViewById(R.id.edit_weight) + editHeight = findViewById(R.id.edit_height) + // 加载并显示当前用户信息 loadUserProfile() // 设置选择头像按钮的点击事件 btnSelectPhoto.setOnClickListener { - openGallery() + checkPermissionAndOpenGallery() } // 设置保存按钮的点击事件 @@ -94,16 +126,37 @@ class EditProfileActivity : AppCompatActivity() { val db = AppDatabase.getDatabase(this@EditProfileActivity) // 使用getUserByUsername查询User对象,其中包含signature字段 val user = db.userDao().getUserByUsername(username).first() + + // 查询UserProfile数据 + val userProfile = db.userProfileDao().getUserProfileByUsername(username).firstOrNull() + withContext(Dispatchers.Main) { if (user != null) { editUsername.setText(user.username) editSignature.setText(user.signature) - // TODO: 添加加载用户头像的逻辑(如果已保存) - // TODO: 需要从数据库或SharedPreferences获取头像路径并加载到imageAvatar + // 加载用户头像的逻辑 + userProfile?.avatarUri?.let { uriString -> + try { + val imageUri = Uri.parse(uriString) + imageAvatar.setImageURI(imageUri) + selectedImageUri = imageUri // 确保 selectedImageUri 也更新以防再次保存 + } catch (e: Exception) { + android.util.Log.e("EditProfileActivity", "Error parsing avatar URI: $uriString", e) + } + } } else { // 用户不存在,这通常不应该发生如果已经登录 Toast.makeText(this@EditProfileActivity, "加载用户信息失败", Toast.LENGTH_SHORT).show() } + + // 显示UserProfile数据 + if (userProfile != null) { + editGender.setText(userProfile.gender) + // 对于 Int 类型,需要转换为 String + if (userProfile.age != 0) editAge.setText(userProfile.age.toString()) + if (userProfile.weight != 0) editWeight.setText(userProfile.weight.toString()) + if (userProfile.height != 0) editHeight.setText(userProfile.height.toString()) + } } } } @@ -115,25 +168,83 @@ class EditProfileActivity : AppCompatActivity() { selectPhotoLauncher.launch(galleryIntent) } + // 检查权限并打开相册 + private fun checkPermissionAndOpenGallery() { + when { + ContextCompat.checkSelfPermission( + this, + android.Manifest.permission.READ_EXTERNAL_STORAGE + ) == PackageManager.PERMISSION_GRANTED -> { + // 权限已授予 + openGallery() + } + shouldShowRequestPermissionRationale(android.Manifest.permission.READ_EXTERNAL_STORAGE) -> { + // 向用户解释为什么需要这个权限,然后再次请求 + Toast.makeText(this, "需要读取存储权限来选择您的头像", Toast.LENGTH_LONG).show() + requestPermissionLauncher.launch(android.Manifest.permission.READ_EXTERNAL_STORAGE) + } + else -> { + // 请求权限 + requestPermissionLauncher.launch(android.Manifest.permission.READ_EXTERNAL_STORAGE) + } + } + } + + // 将图片复制到应用内部存储并返回新URI + private fun copyImageToInternalStorage(uri: Uri): Uri? { + return try { + val inputStream = contentResolver.openInputStream(uri) + val outputFileName = "avatar_${System.currentTimeMillis()}.jpg" + val outputFile = java.io.File(filesDir, outputFileName) + val outputStream = outputFile.outputStream() + + inputStream?.copyTo(outputStream) + inputStream?.close() + outputStream.close() + android.util.Log.d("EditProfileActivity", "Image copied to: ${outputFile.absolutePath}") + Uri.fromFile(outputFile) + } catch (e: Exception) { + android.util.Log.e("EditProfileActivity", "Error copying image to internal storage: ${e.message}", e) + null + } + } + // 保存修改后的个人资料 private fun saveProfile() { currentUsername?.let { username -> val newSignature = editSignature.text.toString().trim() - // 用户名暂时不可编辑,所以只更新个性签名 + + // 获取个人信息输入框的值 + val newGender = editGender.text.toString().trim() + val newAge = editAge.text.toString().trim().toIntOrNull() ?: 0 + val newWeight = editWeight.text.toString().trim().toIntOrNull() ?: 0 + val newHeight = editHeight.text.toString().trim().toIntOrNull() ?: 0 + + // 添加日志输出,确认获取到的数据和当前用户名 + android.util.Log.d("EditProfileActivity", "Current Username: $username") + android.util.Log.d("EditProfileActivity", "New Signature: $newSignature") + android.util.Log.d("EditProfileActivity", "New Gender: $newGender, Age: $newAge, Weight: $newWeight, Height: $newHeight") + android.util.Log.d("EditProfileActivity", "Selected Image URI before save: $selectedImageUri") lifecycleScope.launch(Dispatchers.IO) { val db = AppDatabase.getDatabase(this@EditProfileActivity) // 调用UserDao中的updateSignature方法更新个性签名 db.userDao().updateSignature(username, newSignature) - // 保存头像URI到数据库 - db.userDao().updateAvatarUri(username, selectedImageUri?.toString()) + // 更新UserProfile表中的数据,包括头像URI + val updatedProfile = UserProfile(username, newGender, newAge, newWeight, newHeight, selectedImageUri?.toString()) + android.util.Log.d("EditProfileActivity", "Attempting to update UserProfile: $updatedProfile") + val rowsUpdated = db.userProfileDao().updateUserProfile(updatedProfile) + android.util.Log.d("EditProfileActivity", "UserProfile rows updated: $rowsUpdated for username: $username") // 记录更新的行数 withContext(Dispatchers.Main) { - Toast.makeText(this@EditProfileActivity, "资料保存成功", Toast.LENGTH_SHORT).show() - // 设置Result为RESULT_OK,通知SettingFragment数据已更新 (可选,取决于SettingFragment是否需要知道保存成功) - setResult(Activity.RESULT_OK) - finish() // 保存成功后结束当前Activity,返回Setting页面 + if (rowsUpdated > 0) { + Toast.makeText(this@EditProfileActivity, "资料保存成功", Toast.LENGTH_SHORT).show() + setResult(Activity.RESULT_OK) + finish() + } else { + Toast.makeText(this@EditProfileActivity, "资料保存失败:未找到对应用户档案", Toast.LENGTH_LONG).show() + } } } } diff --git a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/ExerciseDetailActivity.kt b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/ExerciseDetailActivity.kt index d4e540d..262e5d0 100644 --- a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/ExerciseDetailActivity.kt +++ b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/ExerciseDetailActivity.kt @@ -61,7 +61,7 @@ class ExerciseDetailActivity : AppCompatActivity() { // 设置开始训练按钮点击事件,跳转到姿态识别页面 startTrainingButton.setOnClickListener { - val intent = Intent(this, MainActivity::class.java) + val intent = Intent(this, VideoAnalysisActivity::class.java) // 传递当前动作名称给 MainActivity intent.putExtra("current_exercise", exerciseNameFromIntent) startActivity(intent) diff --git a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/GenderSelectionActivity.kt b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/GenderSelectionActivity.kt index 202b14e..fdbaf6c 100644 --- a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/GenderSelectionActivity.kt +++ b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/GenderSelectionActivity.kt @@ -14,11 +14,14 @@ class GenderSelectionActivity : AppCompatActivity() { private lateinit var femaleText: TextView private lateinit var nextButton: MaterialButton private var selectedGender: String? = null + private var username: String? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_gender_selection) + username = intent.getStringExtra("username") + maleButton = findViewById(R.id.maleButton) femaleButton = findViewById(R.id.femaleButton) maleText = findViewById(R.id.maleText) @@ -38,9 +41,9 @@ class GenderSelectionActivity : AppCompatActivity() { } nextButton.setOnClickListener { - // 跳转到年龄选择页面,并传递性别信息 val intent = Intent(this, AgeSelectionActivity::class.java) intent.putExtra("selected_gender", selectedGender) + intent.putExtra("username", username) startActivity(intent) } } diff --git a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/HeightSelectionActivity.kt b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/HeightSelectionActivity.kt index ac9121e..cad8040 100644 --- a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/HeightSelectionActivity.kt +++ b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/HeightSelectionActivity.kt @@ -43,8 +43,10 @@ class HeightSelectionActivity : AppCompatActivity() { selectedGender = intent.getStringExtra("selected_gender") selectedAge = intent.getIntExtra("selected_age", 0) selectedWeight = intent.getIntExtra("selected_weight", 0) - username = intent.getStringExtra("username") + username = intent.getStringExtra("username")?.trim() + android.util.Log.d("HeightSelectionActivity", "Received username: $username") + selectedHeightText = findViewById(R.id.selectedHeightText) heightUnit = findViewById(R.id.heightUnit) height1Above = findViewById(R.id.height1Above) @@ -98,11 +100,22 @@ class HeightSelectionActivity : AppCompatActivity() { val weight = selectedWeight val height = currentHeight val user = username ?: "" + android.util.Log.d("HeightSelectionActivity", "Attempting to save profile for username: $user, gender: $gender, age: $age, weight: $weight, height: $height") lifecycleScope.launch(Dispatchers.IO) { try { val profile = UserProfile(user, gender, age, weight, height) - db.userProfileDao().insertUserProfile(profile) - } catch (_: Exception) {} + android.util.Log.d("HeightSelectionActivity", "Saving UserProfile: $profile") + val rowsUpdated = db.userProfileDao().updateUserProfile(profile) + android.util.Log.d("HeightSelectionActivity", "UserProfile rows updated: $rowsUpdated for username: $user") + if (rowsUpdated == 0) { + db.userProfileDao().insertUserProfile(profile) + android.util.Log.d("HeightSelectionActivity", "UserProfile inserted after update failed for username: $user") + } else { + android.util.Log.d("HeightSelectionActivity", "UserProfile updated successfully for username: $user") + } + } catch (e: Exception) { + android.util.Log.e("HeightSelectionActivity", "Error saving user profile for username: $user: ${e.message}", e) + } } val intent = Intent(this, LoginActivity::class.java) intent.putExtra("selected_gender", selectedGender) diff --git a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/MainTabActivity.kt b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/MainTabActivity.kt index adc9a94..0b93025 100644 --- a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/MainTabActivity.kt +++ b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/MainTabActivity.kt @@ -4,17 +4,24 @@ import android.os.Bundle import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment +import androidx.appcompat.widget.Toolbar +import android.widget.TextView import org.tensorflow.lite.examples.poseestimation.R class MainTabActivity : AppCompatActivity() { private lateinit var navHome: ImageView private lateinit var navData: ImageView private lateinit var navSetting: ImageView + private lateinit var toolbar: Toolbar + private lateinit var toolbarTitle: TextView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main_tab) + toolbar = findViewById(R.id.toolbar) + toolbarTitle = findViewById(R.id.toolbar_title) + navHome = findViewById(R.id.nav_home) navData = findViewById(R.id.nav_data) navSetting = findViewById(R.id.nav_setting) @@ -27,27 +34,28 @@ class MainTabActivity : AppCompatActivity() { // 5. updateNavIcons 方法根据当前选中的导航项(0、1、2)更新三个导航按钮的图标资源 // 默认显示HomeFragment - switchFragment(HomeFragment()) + switchFragment(HomeFragment(), "形动力") updateNavIcons(0) navHome.setOnClickListener { - switchFragment(HomeFragment()) + switchFragment(HomeFragment(), "形动力") updateNavIcons(0) } navData.setOnClickListener { - switchFragment(DataFragment()) + switchFragment(DataFragment(), "形动力") updateNavIcons(1) } navSetting.setOnClickListener { - switchFragment(SettingFragment()) + switchFragment(SettingFragment(), "形动力") updateNavIcons(2) } } - private fun switchFragment(fragment: Fragment) { + private fun switchFragment(fragment: Fragment, title: String) { supportFragmentManager.beginTransaction() .replace(R.id.fragment_container, fragment) .commit() + toolbarTitle.text = title } private fun updateNavIcons(selected: Int) { diff --git a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/Onboarding3Fragment.kt b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/Onboarding3Fragment.kt index 3ad9896..df66273 100644 --- a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/Onboarding3Fragment.kt +++ b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/Onboarding3Fragment.kt @@ -15,11 +15,14 @@ class Onboarding3Fragment : Fragment() { ): View? { val view = inflater.inflate(R.layout.activity_onboarding3, container, false) + val username = arguments?.getString("username") // 获取用户名 + // 找到Start now按钮并设置点击事件 val startButton = view.findViewById(R.id.small_butto_container) startButton.setOnClickListener { // 跳转到性别选择页面 val intent = Intent(requireActivity(), GenderSelectionActivity::class.java) + intent.putExtra("username", username) // 传递用户名 startActivity(intent) requireActivity().finish() // 结束当前的OnboardingActivity } diff --git a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/OnboardingActivity.kt b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/OnboardingActivity.kt index aa1a487..74993ff 100644 --- a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/OnboardingActivity.kt +++ b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/OnboardingActivity.kt @@ -9,7 +9,9 @@ class OnboardingActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.activity_onboarding) + val username = intent.getStringExtra("username") + val viewPager = findViewById(R.id.viewPager) - viewPager.adapter = OnboardingAdapter(this) + viewPager.adapter = OnboardingAdapter(this, username) } } \ No newline at end of file diff --git a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/OnboardingAdapter.kt b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/OnboardingAdapter.kt index 7c91147..cffa2a9 100644 --- a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/OnboardingAdapter.kt +++ b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/OnboardingAdapter.kt @@ -1,18 +1,23 @@ package org.tensorflow.lite.examples.poseestimation +import android.os.Bundle import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.viewpager2.adapter.FragmentStateAdapter -class OnboardingAdapter(activity: FragmentActivity) : FragmentStateAdapter(activity) { +class OnboardingAdapter(activity: FragmentActivity, private val username: String?) : FragmentStateAdapter(activity) { override fun getItemCount(): Int = 3 override fun createFragment(position: Int): Fragment { - return when (position) { + val fragment = when (position) { 0 -> Onboarding1Fragment() 1 -> Onboarding2Fragment() 2 -> Onboarding3Fragment() else -> Onboarding1Fragment() } + fragment.arguments = Bundle().apply { + putString("username", username) + } + return fragment } } \ No newline at end of file diff --git a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/SettingFragment.kt b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/SettingFragment.kt index c1170d2..60f0258 100644 --- a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/SettingFragment.kt +++ b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/SettingFragment.kt @@ -24,6 +24,7 @@ import org.tensorflow.lite.examples.poseestimation.data.UserProfile class SettingFragment : Fragment() { private var isPersonalInfoExpanded = false + private var isMyDataExpanded = false private lateinit var tvUsername: TextView private lateinit var tvSignature: TextView @@ -36,6 +37,11 @@ class SettingFragment : Fragment() { private lateinit var tvHeight: TextView private lateinit var btnAccountInfo: TextView private lateinit var imageAvatar: ImageView + private lateinit var tvTotalTrainings: TextView + private lateinit var tvTotalCalories: TextView + private lateinit var tvTotalTime: TextView + private lateinit var btnMyData: TextView + private lateinit var layoutMyData: View private var currentUsername: String? = null @@ -48,6 +54,8 @@ class SettingFragment : Fragment() { if (isPersonalInfoExpanded) { loadPersonalInfo() } + // 重新加载我的数据 + loadMyData() } } @@ -86,9 +94,16 @@ class SettingFragment : Fragment() { tvHeight = view.findViewById(R.id.tv_height) btnAccountInfo = view.findViewById(R.id.btn_account_info) imageAvatar = view.findViewById(R.id.image_avatar) + tvTotalTrainings = view.findViewById(R.id.tv_total_trainings) + tvTotalCalories = view.findViewById(R.id.tv_total_calories) + tvTotalTime = view.findViewById(R.id.tv_total_time) + btnMyData = view.findViewById(R.id.btn_my_data) + layoutMyData = view.findViewById(R.id.layout_my_data) // 页面创建时加载并显示用户数据 loadUserData() + // 页面创建时加载并显示我的数据 + layoutMyData.visibility = if (isMyDataExpanded) View.VISIBLE else View.GONE // 账户信息点击事件,使用Launcher启动EditProfileActivity btnAccountInfo.setOnClickListener { @@ -106,6 +121,15 @@ class SettingFragment : Fragment() { } } + // 我的数据展开/收起 + btnMyData.setOnClickListener { + isMyDataExpanded = !isMyDataExpanded + layoutMyData.visibility = if (isMyDataExpanded) View.VISIBLE else View.GONE + if (isMyDataExpanded) { + loadMyData() + } + } + // 注销 btnLogout.setOnClickListener { prefs.edit().remove("username").apply() @@ -123,22 +147,30 @@ class SettingFragment : Fragment() { val user = withContext(Dispatchers.IO) { db.userDao().getUserByUsername(username).first() } + // 获取UserProfile数据 + val userProfile = withContext(Dispatchers.IO) { + db.userProfileDao().getUserProfileByUsername(username).firstOrNull() + } + // 在主线程更新UI withContext(Dispatchers.Main) { tvUsername.text = user?.username ?: "-" tvSignature.text = user?.signature ?: "这个人很懒,什么都没写" // 加载并显示用户头像 - user?.avatarUri?.let { uriString -> + userProfile?.avatarUri?.let { uriString -> + android.util.Log.d("SettingFragment", "Attempting to load avatar from URI: $uriString") try { val imageUri = Uri.parse(uriString) imageAvatar.setImageURI(imageUri) + android.util.Log.d("SettingFragment", "Avatar loaded successfully from URI: $imageUri") } catch (e: Exception) { // 处理URI无效或图片加载失败的情况,可以显示默认头像或日志 - e.printStackTrace() + android.util.Log.e("SettingFragment", "Error loading avatar from URI: $uriString", e) imageAvatar.setImageResource(R.drawable.placeholder_image) // 显示默认头像 } } ?: run { // 如果avatarUri为null,也显示默认头像 + android.util.Log.d("SettingFragment", "Avatar URI is null, displaying placeholder.") imageAvatar.setImageResource(R.drawable.placeholder_image) } } @@ -162,14 +194,41 @@ class SettingFragment : Fragment() { tvAge.text = "年龄:${profile.age}" tvWeight.text = "体重:${profile.weight} kg" tvHeight.text = "身高:${profile.height} cm" + // 添加日志输出 + android.util.Log.d("SettingFragment", "UserProfile loaded: $profile") } else { tvGender.text = "性别:-" tvAge.text = "年龄:-" tvWeight.text = "体重:-" tvHeight.text = "身高:-" + // 添加日志输出 + android.util.Log.d("SettingFragment", "UserProfile not found for username: $username") } } } } } + + // 提取加载我的数据的逻辑 + private fun loadMyData() { + currentUsername?.let { username -> + lifecycleScope.launch { + val db = AppDatabase.getDatabase(requireContext()) + val videoAnalysisResults = withContext(Dispatchers.IO) { + db.videoAnalysisResultDao().getVideoAnalysisResultsByUsername(username).first() + } + + withContext(Dispatchers.Main) { + val totalTrainings = videoAnalysisResults.size + // 简化的卡路里和时间估算 (假设每次训练消耗50kcal,每次训练平均5分钟) + val totalCalories = totalTrainings * 50 + val totalMinutes = totalTrainings * 5 + + tvTotalTrainings.text = "总训练次数:$totalTrainings" + tvTotalCalories.text = "总消耗卡路里:$totalCalories kcal" + tvTotalTime.text = "总训练时间:$totalMinutes 分钟" + } + } + } + } } \ No newline at end of file diff --git a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/SignupActivity.kt b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/SignupActivity.kt index d6c9ce7..cffa659 100644 --- a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/SignupActivity.kt +++ b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/SignupActivity.kt @@ -72,15 +72,18 @@ class SignupActivity : AppCompatActivity() { // 创建新用户 val newUser = User(username, password) db.userDao().insertUser(newUser) + android.util.Log.d("SignupActivity", "User inserted: $username") // 同步插入user_profiles表,默认值 val newProfile = UserProfile(username, "-", 0, 0, 0) - db.userProfileDao().insertUserProfile(newProfile) + val profileId = db.userProfileDao().insertUserProfile(newProfile) + android.util.Log.d("SignupActivity", "UserProfile inserted for username: $username with ID: $profileId") runOnUiThread { Toast.makeText(this@SignupActivity, "注册成功", Toast.LENGTH_SHORT).show() // 注册成功后跳转到OnboardingActivity val intent = Intent(this@SignupActivity, OnboardingActivity::class.java) + intent.putExtra("username", username) // 传递用户名 startActivity(intent) finish() // 结束注册界面 } diff --git a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/VideoAnalysisActivity.kt b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/VideoAnalysisActivity.kt new file mode 100644 index 0000000..5244ec3 --- /dev/null +++ b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/VideoAnalysisActivity.kt @@ -0,0 +1,286 @@ +package org.tensorflow.lite.examples.poseestimation + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.graphics.Bitmap +import android.media.MediaMetadataRetriever +import android.net.Uri +import android.os.Bundle +import android.provider.OpenableColumns +import android.view.View +import android.widget.Button +import android.widget.ProgressBar +import android.widget.TextView +import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlinx.coroutines.delay +import org.tensorflow.lite.examples.poseestimation.data.AppDatabase +import org.tensorflow.lite.examples.poseestimation.data.VideoAnalysisResult +import org.tensorflow.lite.examples.poseestimation.ml.MoveNet +import org.tensorflow.lite.examples.poseestimation.ml.PoseDetector +import org.tensorflow.lite.examples.poseestimation.ml.ModelType +import org.tensorflow.lite.examples.poseestimation.data.Device +import org.tensorflow.lite.examples.poseestimation.data.Person +import org.tensorflow.lite.examples.poseestimation.data.KeyPoint +import org.tensorflow.lite.examples.poseestimation.evaluator.DeadliftEvaluator +import org.tensorflow.lite.examples.poseestimation.evaluator.SquatEvaluator +import org.tensorflow.lite.examples.poseestimation.evaluator.ExerciseEvaluator +import org.tensorflow.lite.examples.poseestimation.evaluator.PlankEvaluator +import org.tensorflow.lite.examples.poseestimation.evaluator.PullUpEvaluator +import org.tensorflow.lite.examples.poseestimation.evaluator.PushUpEvaluator + +class VideoAnalysisActivity : AppCompatActivity() { + + private lateinit var tvExerciseName: TextView + private lateinit var btnSelectVideo: Button + private lateinit var tvAnalysisStatus: TextView + private lateinit var progressBarAnalysis: ProgressBar + + private var currentExerciseType: String? = null + private var currentUsername: String? = null + + private lateinit var poseDetector: PoseDetector + + // 用于存储复制到内部存储后的视频URI + private var internalVideoUri: Uri? = null + + // Launcher for selecting video from local storage + private val selectVideoLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode == Activity.RESULT_OK) { + val videoUri: Uri? = result.data?.data + videoUri?.let { + // 将视频复制到内部存储 + lifecycleScope.launch(Dispatchers.IO) { + val copiedUri = copyVideoToInternalStorage(it) + withContext(Dispatchers.Main) { + if (copiedUri != null) { + internalVideoUri = copiedUri + android.util.Log.d("VideoAnalysisActivity", "视频已复制到内部存储: $internalVideoUri") + tvAnalysisStatus.text = "已选择视频: ${getFileName(it)},开始分析..." + tvAnalysisStatus.visibility = View.VISIBLE + progressBarAnalysis.visibility = View.VISIBLE + progressBarAnalysis.progress = 0 + startVideoAnalysis(internalVideoUri!!, currentExerciseType) + } else { + Toast.makeText(this@VideoAnalysisActivity, "视频复制失败,请重试", Toast.LENGTH_SHORT).show() + tvAnalysisStatus.text = "视频复制失败" + tvAnalysisStatus.visibility = View.VISIBLE + progressBarAnalysis.visibility = View.GONE + } + } + } + } ?: run { + Toast.makeText(this, "未选择视频", Toast.LENGTH_SHORT).show() + tvAnalysisStatus.text = "未选择视频" + tvAnalysisStatus.visibility = View.VISIBLE + progressBarAnalysis.visibility = View.GONE + } + } else { + Toast.makeText(this, "取消选择视频", Toast.LENGTH_SHORT).show() + tvAnalysisStatus.text = "取消选择视频" + tvAnalysisStatus.visibility = View.VISIBLE + progressBarAnalysis.visibility = View.GONE + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_video_analysis) + + // 获取当前登录用户名 (假设用SharedPreferences存储) + val prefs = getSharedPreferences("user_prefs", Context.MODE_PRIVATE) + currentUsername = prefs.getString("username", null)?.trim() + + if (currentUsername == null) { + Toast.makeText(this, "未登录,请重新登录", Toast.LENGTH_SHORT).show() + startActivity(Intent(this, LoginActivity::class.java)) + finish() + return + } + + // Get exercise name from intent + currentExerciseType = intent.getStringExtra("current_exercise") + + // Initialize views + tvExerciseName = findViewById(R.id.tv_exercise_name) + btnSelectVideo = findViewById(R.id.btn_select_video) + tvAnalysisStatus = findViewById(R.id.tv_analysis_status) + progressBarAnalysis = findViewById(R.id.progress_bar_analysis) + + // Initialize PoseDetector (using MoveNet Lightning by default) + poseDetector = MoveNet.create(this, Device.CPU, ModelType.Lightning) + + // Set exercise name + tvExerciseName.text = currentExerciseType ?: "未知动作" + + // Set click listener for video selection button + btnSelectVideo.setOnClickListener { + openVideoPicker() + } + } + + // Override onDestroy to close the poseDetector + override fun onDestroy() { + super.onDestroy() + poseDetector.close() + } + + private fun openVideoPicker() { + val intent = Intent(Intent.ACTION_GET_CONTENT) + intent.type = "video/*" + intent.addCategory(Intent.CATEGORY_OPENABLE) + selectVideoLauncher.launch(Intent.createChooser(intent, "选择视频")) + } + + // Helper function to get file name from Uri + private fun getFileName(uri: Uri): String { + var result: String? = null + if (uri.scheme == "content") { + val cursor = contentResolver.query(uri, null, null, null, null) + cursor?.use { + if (it.moveToFirst()) { + val nameIndex = it.getColumnIndex(OpenableColumns.DISPLAY_NAME) + if (nameIndex != -1) { + result = it.getString(nameIndex) + } + } + } + } + if (result == null) { + result = uri.path + val cut = result?.lastIndexOf('/') + if (cut != -1) { + result = result?.substring(cut!! + 1) + } + } + return result ?: "未知文件" + } + + // 将视频复制到应用内部存储并返回新URI + private fun copyVideoToInternalStorage(uri: Uri): Uri? { + return try { + val inputStream = contentResolver.openInputStream(uri) + val outputFileName = "video_${System.currentTimeMillis()}.mp4" + val outputFile = java.io.File(filesDir, outputFileName) + val outputStream = outputFile.outputStream() + + inputStream?.copyTo(outputStream) + inputStream?.close() + outputStream.close() + android.util.Log.d("VideoAnalysisActivity", "视频已复制到: ${outputFile.absolutePath}") + Uri.fromFile(outputFile) + } catch (e: Exception) { + android.util.Log.e("VideoAnalysisActivity", "复制视频到内部存储出错: ${e.message}", e) + null + } + } + + // Placeholder for video analysis logic + private fun startVideoAnalysis(videoUri: Uri, exerciseType: String?) { + val db = AppDatabase.getDatabase(applicationContext) + val videoAnalysisResultDao = db.videoAnalysisResultDao() + + lifecycleScope.launch(Dispatchers.IO) { + withContext(Dispatchers.Main) { + tvAnalysisStatus.text = "正在初始化姿态识别模型..." + btnSelectVideo.isEnabled = false // Disable button during analysis + } + + val retriever = MediaMetadataRetriever() + retriever.setDataSource(applicationContext, videoUri) + val durationMs = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLong() ?: 0 + + // Process frames at a reasonable interval, e.g., 10 frames per second for analysis + val frameRate = 10 // frames per second + val frameIntervalUs = 1_000_000L / frameRate // interval in microseconds + + var currentFrameTimeUs = 0L + + // Initialize evaluator based on exercise type + val evaluator: ExerciseEvaluator? = when (exerciseType) { + "硬拉" -> DeadliftEvaluator() + "深蹲" -> SquatEvaluator() + "平板支撑" -> PlankEvaluator() + "引体向上" -> PullUpEvaluator() + "俯卧撑" -> PushUpEvaluator() + // TODO: Add evaluators for other exercises + else -> null + } + + while (currentFrameTimeUs <= durationMs * 1000) { // durationMs is in milliseconds, getFrameAtTime expects microseconds + val bitmap = retriever.getFrameAtTime(currentFrameTimeUs, MediaMetadataRetriever.OPTION_CLOSEST_SYNC) + + bitmap?.let { frameBitmap -> + val persons = poseDetector.estimatePoses(frameBitmap) + val person = persons.firstOrNull() + + person?.let { p -> + if (p.score > 0.3) { // Only consider poses with reasonable confidence + // Pass keypoints to the evaluator + evaluator?.evaluateFrame(p.keyPoints) + } + } + frameBitmap.recycle() // Recycle bitmap to free memory + } + + val progress = ((currentFrameTimeUs.toFloat() / (durationMs * 1000)) * 100).toInt() + withContext(Dispatchers.Main) { + progressBarAnalysis.progress = progress + tvAnalysisStatus.text = "正在分析视频帧: ${currentFrameTimeUs / 1000}ms / ${durationMs}ms" + } + + currentFrameTimeUs += frameIntervalUs + } + retriever.release() + + withContext(Dispatchers.Main) { + tvAnalysisStatus.text = "姿态识别完成,正在评估..." + } + + // --- Pose Evaluation and Scoring --- + + val finalScore: Float + val evaluation: String + + if (evaluator != null) { + finalScore = evaluator.getFinalScore() + evaluation = evaluator.getFinalEvaluation(finalScore) + } else { + finalScore = 0f + evaluation = "暂不支持该动作的详细评估。" + } + + // Store result in database + val result = VideoAnalysisResult( + username = currentUsername ?: "未知用户", + exerciseType = exerciseType ?: "未知运动", + videoUri = videoUri.toString(), // 使用已经复制到内部存储的URI + score = finalScore, + evaluation = evaluation, + timestamp = System.currentTimeMillis() + ) + + videoAnalysisResultDao.insertVideoAnalysisResult(result) + + withContext(Dispatchers.Main) { + tvAnalysisStatus.text = "分析完成!正在显示结果..." + progressBarAnalysis.visibility = View.GONE + btnSelectVideo.isEnabled = true // Re-enable button + + val intent = Intent(this@VideoAnalysisActivity, VideoAnalysisResultActivity::class.java).apply { + putExtra("exercise_name", currentExerciseType) + putExtra("score", finalScore) + putExtra("evaluation", evaluation) + } + startActivity(intent) + finish() // Finish this activity so user can't go back to it + } + } + } +} \ No newline at end of file diff --git a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/VideoAnalysisAdapter.kt b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/VideoAnalysisAdapter.kt new file mode 100644 index 0000000..bb8c5d6 --- /dev/null +++ b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/VideoAnalysisAdapter.kt @@ -0,0 +1,71 @@ +package org.tensorflow.lite.examples.poseestimation + +import android.content.Intent +import android.net.Uri +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import android.widget.Toast +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import androidx.core.content.FileProvider +import org.tensorflow.lite.examples.poseestimation.data.VideoAnalysisResult +import java.io.File +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +class VideoAnalysisAdapter(private val results: List) : RecyclerView.Adapter() { + + class VideoAnalysisViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val tvExerciseType: TextView = itemView.findViewById(R.id.tv_exercise_type) + val tvScore: TextView = itemView.findViewById(R.id.tv_score) + val tvEvaluation: TextView = itemView.findViewById(R.id.tv_evaluation) + val videoThumbnail: ImageView = itemView.findViewById(R.id.video_thumbnail) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VideoAnalysisViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.item_video_analysis_card, parent, false) + return VideoAnalysisViewHolder(view) + } + + override fun onBindViewHolder(holder: VideoAnalysisViewHolder, position: Int) { + val result = results[position] + holder.tvExerciseType.text = "运动类型:${result.exerciseType}" + holder.tvScore.text = "${result.score.toInt()} 分" + holder.tvEvaluation.text = "评价:${result.evaluation}" + + Glide.with(holder.itemView.context) + .load(Uri.parse(result.videoUri)) + .placeholder(R.drawable.placeholder_image) + .error(R.drawable.placeholder_image) + .into(holder.videoThumbnail) + + android.util.Log.d("VideoAnalysisAdapter", "Loading video thumbnail for URI: ${result.videoUri}") + + holder.videoThumbnail.setOnClickListener { + try { + val videoFile = File(Uri.parse(result.videoUri).path) + val context = holder.itemView.context + + val contentUri: Uri = FileProvider.getUriForFile( + context, + "${context.packageName}.fileprovider", + videoFile + ) + + val intent = Intent(Intent.ACTION_VIEW) + intent.setDataAndType(contentUri, "video/*") + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + context.startActivity(intent) + } catch (e: Exception) { + android.util.Log.e("VideoAnalysisAdapter", "无法播放视频: ${result.videoUri}, 错误: ${e.message}", e) + Toast.makeText(holder.itemView.context, "无法播放视频,请检查文件或权限", Toast.LENGTH_SHORT).show() + } + } + } + + override fun getItemCount(): Int = results.size +} \ No newline at end of file diff --git a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/VideoAnalysisResultActivity.kt b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/VideoAnalysisResultActivity.kt new file mode 100644 index 0000000..5295a9a --- /dev/null +++ b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/VideoAnalysisResultActivity.kt @@ -0,0 +1,30 @@ +package org.tensorflow.lite.examples.poseestimation + +import android.os.Bundle +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity + +class VideoAnalysisResultActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_video_analysis_result) + + val tvExerciseName = findViewById(R.id.tv_result_exercise_name) + val tvScore = findViewById(R.id.tv_result_score) + val tvEvaluation = findViewById(R.id.tv_result_evaluation) + + // 从 Intent 中获取数据 + val exerciseName = intent.getStringExtra("exercise_name") + val score = intent.getFloatExtra("score", 0f) + val evaluation = intent.getStringExtra("evaluation") + + // 设置数据显示 + tvExerciseName.text = exerciseName ?: "未知动作" + tvScore.text = String.format("%.1f", score) // 格式化得分,保留一位小数 + tvEvaluation.text = evaluation ?: "暂无评价" + + // 设置标题 (可选) + supportActionBar?.title = "分析结果" + } +} \ No newline at end of file diff --git a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/WeightSelectionActivity.kt b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/WeightSelectionActivity.kt index 1384a9b..d363e93 100644 --- a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/WeightSelectionActivity.kt +++ b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/WeightSelectionActivity.kt @@ -20,6 +20,7 @@ class WeightSelectionActivity : AppCompatActivity() { private var selectedGender: String? = null private var selectedAge: Int = 0 + private var username: String? = null private var currentWeight = 54 private var lastY: Float = 0f @@ -34,6 +35,7 @@ class WeightSelectionActivity : AppCompatActivity() { // 获取从上一个页面传递的数据 selectedGender = intent.getStringExtra("selected_gender") selectedAge = intent.getIntExtra("selected_age", 0) + username = intent.getStringExtra("username") // 初始化视图 selectedWeightText = findViewById(R.id.selectedWeightText) @@ -87,6 +89,7 @@ class WeightSelectionActivity : AppCompatActivity() { intent.putExtra("selected_gender", selectedGender) intent.putExtra("selected_age", selectedAge) intent.putExtra("selected_weight", currentWeight) + intent.putExtra("username", username) startActivity(intent) finish() } @@ -94,6 +97,7 @@ class WeightSelectionActivity : AppCompatActivity() { backButton.setOnClickListener { val intent = Intent(this, AgeSelectionActivity::class.java) intent.putExtra("selected_gender", selectedGender) + intent.putExtra("username", username) startActivity(intent) finish() } diff --git a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/data/AppDatabase.kt b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/data/AppDatabase.kt index c12e6a5..4ddc78d 100644 --- a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/data/AppDatabase.kt +++ b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/data/AppDatabase.kt @@ -4,16 +4,33 @@ import android.content.Context import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase -@Database(entities = [User::class, UserProfile::class], version = 4, exportSchema = false) +@Database(entities = [User::class, UserProfile::class, VideoAnalysisResult::class], version = 6, exportSchema = false) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao abstract fun userProfileDao(): UserProfileDao + abstract fun videoAnalysisResultDao(): VideoAnalysisResultDao companion object { @Volatile private var INSTANCE: AppDatabase? = null + private val MIGRATION_4_5 = object : Migration(4, 5) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + "CREATE TABLE IF NOT EXISTS `video_analysis_results` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `username` TEXT NOT NULL, `exerciseType` TEXT NOT NULL, `videoUri` TEXT NOT NULL, `score` REAL NOT NULL, `evaluation` TEXT NOT NULL, `timestamp` INTEGER NOT NULL)" + ) + } + } + + private val MIGRATION_5_6 = object : Migration(5, 6) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE user_profiles ADD COLUMN avatarUri TEXT") + } + } + fun getDatabase(context: Context): AppDatabase { return INSTANCE ?: synchronized(this) { val instance = Room.databaseBuilder( @@ -21,7 +38,7 @@ abstract class AppDatabase : RoomDatabase() { AppDatabase::class.java, "app_database" ) - .fallbackToDestructiveMigration() + .addMigrations(MIGRATION_4_5, MIGRATION_5_6) .build() INSTANCE = instance instance diff --git a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/data/UserProfile.kt b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/data/UserProfile.kt index 07be1ce..e76b8df 100644 --- a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/data/UserProfile.kt +++ b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/data/UserProfile.kt @@ -9,5 +9,6 @@ data class UserProfile( val gender: String, val age: Int, val weight: Int, - val height: Int + val height: Int, + val avatarUri: String? = null // 新增:用户头像URI ) \ No newline at end of file diff --git a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/data/UserProfileDao.kt b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/data/UserProfileDao.kt index 70456fb..cb440b5 100644 --- a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/data/UserProfileDao.kt +++ b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/data/UserProfileDao.kt @@ -3,12 +3,16 @@ package org.tensorflow.lite.examples.poseestimation.data import androidx.room.Dao import androidx.room.Insert import androidx.room.Query +import androidx.room.Update import kotlinx.coroutines.flow.Flow @Dao interface UserProfileDao { @Insert - fun insertUserProfile(profile: UserProfile) + fun insertUserProfile(profile: UserProfile): Long + + @Update + fun updateUserProfile(profile: UserProfile): Int @Query("SELECT * FROM user_profiles WHERE username = :username") fun getUserProfileByUsername(username: String): Flow diff --git a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/data/VideoAnalysisResult.kt b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/data/VideoAnalysisResult.kt new file mode 100644 index 0000000..60decaa --- /dev/null +++ b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/data/VideoAnalysisResult.kt @@ -0,0 +1,15 @@ +package org.tensorflow.lite.examples.poseestimation.data + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "video_analysis_results") +data class VideoAnalysisResult( + @PrimaryKey(autoGenerate = true) val id: Long = 0, + val username: String, + val exerciseType: String, + val videoUri: String, + val score: Float, // Simplified score for now + val evaluation: String, + val timestamp: Long // Timestamp for when the analysis was performed +) \ No newline at end of file diff --git a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/data/VideoAnalysisResultDao.kt b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/data/VideoAnalysisResultDao.kt new file mode 100644 index 0000000..be6778b --- /dev/null +++ b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/data/VideoAnalysisResultDao.kt @@ -0,0 +1,19 @@ +package org.tensorflow.lite.examples.poseestimation.data + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query +import kotlinx.coroutines.flow.Flow + +@Dao +interface VideoAnalysisResultDao { + @Insert + fun insertVideoAnalysisResult(result: VideoAnalysisResult): Long + + @Query("SELECT * FROM video_analysis_results WHERE username = :username ORDER BY timestamp DESC") + fun getVideoAnalysisResultsByUsername(username: String): Flow> + + // Optional: Get results for a specific exercise type + @Query("SELECT * FROM video_analysis_results WHERE username = :username AND exerciseType = :exerciseType ORDER BY timestamp DESC") + fun getVideoAnalysisResultsByUsernameAndExerciseType(username: String, exerciseType: String): Flow> +} \ No newline at end of file diff --git a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/evaluator/DeadliftEvaluator.kt b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/evaluator/DeadliftEvaluator.kt new file mode 100644 index 0000000..9fa843f --- /dev/null +++ b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/evaluator/DeadliftEvaluator.kt @@ -0,0 +1,239 @@ +package org.tensorflow.lite.examples.poseestimation.evaluator + +import org.tensorflow.lite.examples.poseestimation.data.KeyPoint +import org.tensorflow.lite.examples.poseestimation.data.BodyPart +import kotlin.math.abs +import kotlin.math.roundToInt + +// 定义硬拉动作的阶段 +enum class DeadliftState { + START, // 起始姿态 + DESCENT, // 下降过程 + BOTTOM, // 底部姿态 + ASCENT, // 上升过程 + LOCKOUT // 锁定(完成)姿态 +} + +data class EvaluationMessage( + val message: String, + val isError: Boolean = false +) + +class DeadliftEvaluator : ExerciseEvaluator { + + private var currentState: DeadliftState = DeadliftState.START + private var totalScore: Float = 0f + private var frameCount: Int = 0 + private var evaluationMessages: MutableList = mutableListOf() + private var repCount: Int = 0 + private var isRepCounting: Boolean = false + + // 硬拉关键角度阈值 (示例值,需要根据实际模型和标准进行调整) + private val HIP_ANGLE_START_MAX = 170f // 站立时髋关节角度 + private val KNEE_ANGLE_START_MAX = 170f // 站立时膝关节角度 + private val TORSO_ANGLE_START_MAX = 10f // 站立时躯干与垂直方向最大夹角 + + private val HIP_ANGLE_BOTTOM_MIN = 70f // 硬拉底部髋关节最小角度 + private val KNEE_ANGLE_BOTTOM_MIN = 90f // 硬拉底部膝关节最小角度 + private val TORSO_ANGLE_BOTTOM_MAX = 45f // 硬拉底部躯干与垂直方向最大夹角 + + private val BACK_STRAIGHT_THRESHOLD = 160f // 躯干挺直角度阈值 (肩-髋-膝角度) + + // Helper function to calculate angle between three keypoints (A-B-C) + // B is the vertex of the angle + private fun calculateAngle(A: KeyPoint, B: KeyPoint, C: KeyPoint): Float { + // Use the abttPoints function from KeyPoint.kt + return B.abttPoints(A, B, C) + } + + override fun evaluateFrame(keyPoints: List) { + if (keyPoints.isEmpty()) { + evaluationMessages.add(EvaluationMessage("未检测到关键点,无法评估。", true)) + return + } + + frameCount++ + + // 获取硬拉所需关键点 + val leftShoulder = keyPoints.find { it.bodyPart == BodyPart.LEFT_SHOULDER } + val rightShoulder = keyPoints.find { it.bodyPart == BodyPart.RIGHT_SHOULDER } + val leftHip = keyPoints.find { it.bodyPart == BodyPart.LEFT_HIP } + val rightHip = keyPoints.find { it.bodyPart == BodyPart.RIGHT_HIP } + val leftKnee = keyPoints.find { it.bodyPart == BodyPart.LEFT_KNEE } + val rightKnee = keyPoints.find { it.bodyPart == BodyPart.RIGHT_KNEE } + val leftAnkle = keyPoints.find { it.bodyPart == BodyPart.LEFT_ANKLE } + val rightAnkle = keyPoints.find { it.bodyPart == BodyPart.RIGHT_ANKLE } + + // 确保所有关键点都存在,如果缺失,则跳过此帧或给出警告 + if (leftShoulder == null || rightShoulder == null || leftHip == null || rightHip == null || + leftKnee == null || rightKnee == null || leftAnkle == null || rightAnkle == null) { + evaluationMessages.add(EvaluationMessage("关键点缺失,评估可能不准确。", true)) + return + } + + // 计算左右平均关键点,提高稳定性 + val midShoulder = KeyPoint( + BodyPart.NOSE, // 使用 NOSE 作为占位符,因为没有一个"中肩"的 BodyPart + android.graphics.PointF( + (leftShoulder.coordinate.x + rightShoulder.coordinate.x) / 2, + (leftShoulder.coordinate.y + rightShoulder.coordinate.y) / 2 + ), + (leftShoulder.score + rightShoulder.score) / 2 + ) + val midHip = KeyPoint( + BodyPart.NOSE, // 同样,使用 NOSE 作为占位符 + android.graphics.PointF( + (leftHip.coordinate.x + rightHip.coordinate.x) / 2, + (leftHip.coordinate.y + rightHip.coordinate.y) / 2 + ), + (leftHip.score + rightHip.score) / 2 + ) + val midKnee = KeyPoint( + BodyPart.NOSE, // 同样,使用 NOSE 作为占位符 + android.graphics.PointF( + (leftKnee.coordinate.x + rightKnee.coordinate.x) / 2, + (leftKnee.coordinate.y + rightKnee.coordinate.y) / 2 + ), + (leftKnee.score + rightKnee.score) / 2 + ) + val midAnkle = KeyPoint( + BodyPart.NOSE, // 同样,使用 NOSE 作为占位符 + android.graphics.PointF( + (leftAnkle.coordinate.x + rightAnkle.coordinate.x) / 2, + (leftAnkle.coordinate.y + rightAnkle.coordinate.y) / 2 + ), + (leftAnkle.score + rightAnkle.score) / 2 + ) + + + // 计算核心角度 + val hipAngle = calculateAngle(midShoulder, midHip, midKnee) // 肩-髋-膝 + val kneeAngle = calculateAngle(midHip, midKnee, midAnkle) // 髋-膝-踝 + + // 躯干角度 (这里简化为肩与髋的y坐标差与x坐标差的反正切,并转换为相对于垂直线的角度) + // 更准确的躯干角度需要更复杂的向量计算,例如通过肩-髋连线与竖直方向的夹角 + val torsoAngle = abs(midShoulder.abtPoints(midHip).second - 90f) // 假设 midShoulder.abtPoints(midHip).second 是与Y轴的夹角,减去90得到与X轴的夹角,再取绝对值,但这里我们想要与垂直线的夹角,所以需要调整 + + // 简化的躯干角度:直接使用肩和髋的y坐标差与x坐标差的反正切 + val dxTorso = midHip.coordinate.x - midShoulder.coordinate.x + val dyTorso = midHip.coordinate.y - midShoulder.coordinate.y + val torsoAngleVertical = Math.toDegrees(kotlin.math.atan2(dxTorso.toDouble(), dyTorso.toDouble())).toFloat() // 与y轴正方向的夹角 + + // 如果要与垂直方向的夹角,通常是 90 - atan2(dy, dx) + // 或者直接根据象限判断 + val realTorsoAngle = abs(torsoAngleVertical) // 简化处理,取绝对值 + + var frameScore = 0f + var frameEvaluation = "" + + when (currentState) { + DeadliftState.START -> { + // 评估起始姿态 + if (hipAngle > HIP_ANGLE_START_MAX && kneeAngle > KNEE_ANGLE_START_MAX && realTorsoAngle < TORSO_ANGLE_START_MAX) { + frameEvaluation = "很棒的起始姿态!准备好向下,让身体进入训练模式。" + isRepCounting = false // 重置,等待下一次下降 + currentState = DeadliftState.DESCENT + frameScore = 300f + } else { + frameEvaluation = "小提示:起始姿态可以再调整一下。试着站得更直,核心收得更紧,为硬拉做好准备!" + evaluationMessages.add(EvaluationMessage(frameEvaluation, true)) + } + } + DeadliftState.DESCENT -> { + // 评估下降过程 + if (hipAngle < HIP_ANGLE_START_MAX && kneeAngle < KNEE_ANGLE_START_MAX) { // 髋和膝盖开始弯曲 + if (realTorsoAngle < BACK_STRAIGHT_THRESHOLD) { // 检查背部是否挺直 + frameEvaluation = "下降得很稳!记得保持背部挺直,感受髋部的发力。" + currentState = DeadliftState.BOTTOM + frameScore = 300f + } else { + frameEvaluation = "下降时背部有点弓起哦!试着挺胸收腹,保持脊柱中立,会更安全有效。" + evaluationMessages.add(EvaluationMessage(frameEvaluation, true)) + } + } else { + frameEvaluation = "继续向下,保持动作流畅,感受肌肉的拉伸。" + } + } + DeadliftState.BOTTOM -> { + // 评估底部姿态 + if (hipAngle >= HIP_ANGLE_BOTTOM_MIN && hipAngle <= HIP_ANGLE_START_MAX && + kneeAngle >= KNEE_ANGLE_BOTTOM_MIN && kneeAngle <= KNEE_ANGLE_START_MAX && + realTorsoAngle <= TORSO_ANGLE_BOTTOM_MAX) { + frameEvaluation = "底部姿态非常稳定!现在准备向上发力,让身体爆发起来!" + currentState = DeadliftState.ASCENT + frameScore = 450f + } else { + frameEvaluation = "底部姿态可以再优化一下。尝试让髋部再低一些,膝盖不要超过脚尖太多,同时保持背部挺直。你离完美不远了!" + if (hipAngle < HIP_ANGLE_BOTTOM_MIN) evaluationMessages.add(EvaluationMessage("髋部下降不足。", true)) + if (kneeAngle < KNEE_ANGLE_BOTTOM_MIN) evaluationMessages.add(EvaluationMessage("膝盖弯曲不足。", true)) + if (realTorsoAngle > TORSO_ANGLE_BOTTOM_MAX) evaluationMessages.add(EvaluationMessage("注意!你的背部有些弓起或者过度前倾了。记得整个过程都要保持背部挺直,这是硬拉的关键!", true)) + } + } + DeadliftState.ASCENT -> { + // 评估上升过程 + if (hipAngle > HIP_ANGLE_BOTTOM_MIN && kneeAngle > KNEE_ANGLE_BOTTOM_MIN) { // 髋和膝盖开始伸展 + if (realTorsoAngle < BACK_STRAIGHT_THRESHOLD) { // 检查背部是否挺直 + frameEvaluation = "向上发力非常有力!继续保持背部挺直,让髋部和膝盖同步伸展,爆发力十足!" + currentState = DeadliftState.LOCKOUT + frameScore = 300f + } else { + frameEvaluation = "上升时背部有些弓起了。集中注意力,保持背部挺直,用臀部发力带动身体向上。" + evaluationMessages.add(EvaluationMessage(frameEvaluation, true)) + } + } else { + frameEvaluation = "加油,继续向上,把身体完全推回起始位置!" + } + } + DeadliftState.LOCKOUT -> { + // 评估锁定姿态 + if (hipAngle > HIP_ANGLE_START_MAX - 5 && kneeAngle > KNEE_ANGLE_START_MAX - 5 && realTorsoAngle < TORSO_ANGLE_START_MAX + 5) { + frameEvaluation = "完美完成一次!锁定姿态非常棒,全身收紧,力量感十足!" + if (!isRepCounting) { + repCount++ + isRepCounting = true + evaluationMessages.add(EvaluationMessage("恭喜你,又完成了一次硬拉!目前累计完成了 $repCount 次,继续保持!")) + } + currentState = DeadliftState.START // 完成一次后回到起始状态,准备下一次 + frameScore = 150f + } else { + frameEvaluation = "快完成了!在顶部时,记得让髋部和膝盖完全伸展,轻轻收紧臀部,肩膀向后收拢,充分锁定。" + if (hipAngle < HIP_ANGLE_START_MAX - 5 || kneeAngle < KNEE_ANGLE_START_MAX - 5) evaluationMessages.add(EvaluationMessage("顶部锁定还不够充分哦,髋部和膝盖可以再伸展一点。", true)) + if (realTorsoAngle > TORSO_ANGLE_START_MAX + 5) evaluationMessages.add(EvaluationMessage("注意了!在锁定阶段,身体可能有点过度后仰或者背部没有完全挺直。保持核心收紧,稳稳地完成动作。", true)) + } + } + } + totalScore += frameScore // 累加每帧分数 + evaluationMessages.add(EvaluationMessage(frameEvaluation)) // 添加每帧的评估 + } + + override fun getFinalScore(): Float { + val rawScore = if (frameCount > 0) totalScore / frameCount else 0f + val roundedScore = (rawScore / 10.0f).roundToInt() * 10.0f + return roundedScore.coerceIn(0f, 100f) + } + + override fun getFinalEvaluation(finalScore: Float): String { + val uniqueMessages = evaluationMessages.map { it.message }.distinct().joinToString("\n") + val errors = evaluationMessages.filter { it.isError }.map { it.message }.distinct() + + val overallEvaluationBuilder = StringBuilder() + + if (errors.isEmpty()) { + when (finalScore.toInt()) { + in 90..100 -> overallEvaluationBuilder.append("太棒了!你的硬拉动作几乎完美无瑕,姿态标准,力量十足!继续保持!") + in 70..89 -> overallEvaluationBuilder.append("非常不错的硬拉!动作基本流畅,姿态也比较到位,再稍加注意细节就能更完美!") + in 50..69 -> overallEvaluationBuilder.append("硬拉动作有进步空间哦!虽然有些地方做得不错,但还需要多练习,让姿态更稳定、发力更集中。") + in 30..49 -> overallEvaluationBuilder.append("本次硬拉需要更多练习。动作中存在一些明显的姿态问题,这会影响训练效果和安全性。") + else -> overallEvaluationBuilder.append("硬拉动作仍需大量改进。请务必仔细对照标准,从基础开始练习,避免受伤。") + } + } else { + overallEvaluationBuilder.append("本次硬拉分析完成!发现了一些可以改进的地方:\n") + overallEvaluationBuilder.append(errors.joinToString("\n")) + } + + overallEvaluationBuilder.append("\n\n以下是本次训练的详细分析过程,希望能帮助你更好地理解和改进:\n") + overallEvaluationBuilder.append(uniqueMessages) + + return overallEvaluationBuilder.toString() + } +} \ No newline at end of file diff --git a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/evaluator/ExerciseEvaluator.kt b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/evaluator/ExerciseEvaluator.kt new file mode 100644 index 0000000..ca3443d --- /dev/null +++ b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/evaluator/ExerciseEvaluator.kt @@ -0,0 +1,9 @@ +package org.tensorflow.lite.examples.poseestimation.evaluator + +import org.tensorflow.lite.examples.poseestimation.data.KeyPoint + +interface ExerciseEvaluator { + fun evaluateFrame(keyPoints: List) + fun getFinalScore(): Float + fun getFinalEvaluation(finalScore: Float): String +} \ No newline at end of file diff --git a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/evaluator/PlankEvaluator.kt b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/evaluator/PlankEvaluator.kt new file mode 100644 index 0000000..60ed828 --- /dev/null +++ b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/evaluator/PlankEvaluator.kt @@ -0,0 +1,198 @@ +package org.tensorflow.lite.examples.poseestimation.evaluator + +import org.tensorflow.lite.examples.poseestimation.data.KeyPoint +import org.tensorflow.lite.examples.poseestimation.data.BodyPart +import kotlin.math.abs +import kotlin.math.roundToInt + +// 定义平板支撑动作的阶段 +enum class PlankState { + HOLDING, // 保持姿态 + ERROR // 错误姿态 +} + +class PlankEvaluator : ExerciseEvaluator { + + private var currentState: PlankState = PlankState.HOLDING + private var totalScore: Float = 0f + private var frameCount: Int = 0 + private var evaluationMessages: MutableList = mutableListOf() + private var timeInCorrectPoseMs: Long = 0L // 记录正确姿态的持续时间 + private var lastFrameTimestamp: Long = 0L + + // 平板支撑关键角度阈值 (示例值,需要根据实际模型和标准进行调整) + private val HIP_SHOULDER_ANKLE_MIN = 160f // 髋-肩-踝角度,用于判断身体是否呈直线 + private val HIP_SHOULDER_ANKLE_MAX = 185f // 允许略微的弧度 + private val HIP_SAG_THRESHOLD_PERCENT = 0.05f // 臀部下沉阈值(相对于肩-踝距离的百分比) + private val HIP_PIKE_THRESHOLD_PERCENT = 0.05f // 臀部过高阈值 + + // Helper function to calculate angle between three keypoints (A-B-C) + // B is the vertex of the angle + private fun calculateAngle(A: KeyPoint, B: KeyPoint, C: KeyPoint): Float { + return B.abttPoints(A, B, C) + } + + override fun evaluateFrame(keyPoints: List) { + if (keyPoints.isEmpty()) { + evaluationMessages.add(EvaluationMessage("未检测到关键点,无法评估。", true)) + return + } + + frameCount++ + val currentTimestamp = System.currentTimeMillis() // 假设每帧的时间间隔可以通过外部传入或自行估算 + if (lastFrameTimestamp != 0L) { + val deltaTimeMs = currentTimestamp - lastFrameTimestamp + // 假设视频帧率稳定,或者直接使用帧间隔(例如100ms一帧) + // 这里简化处理,直接使用一个固定的帧分数,而不是基于时间 + } + lastFrameTimestamp = currentTimestamp + + // 获取平板支撑所需关键点 + val leftShoulder = keyPoints.find { it.bodyPart == BodyPart.LEFT_SHOULDER } + val rightShoulder = keyPoints.find { it.bodyPart == BodyPart.RIGHT_SHOULDER } + val leftHip = keyPoints.find { it.bodyPart == BodyPart.LEFT_HIP } + val rightHip = keyPoints.find { it.bodyPart == BodyPart.RIGHT_HIP } + val leftAnkle = keyPoints.find { it.bodyPart == BodyPart.LEFT_ANKLE } + val rightAnkle = keyPoints.find { it.bodyPart == BodyPart.RIGHT_ANKLE } + val nose = keyPoints.find { it.bodyPart == BodyPart.NOSE } + + // 确保所有关键点都存在 + if (leftShoulder == null || rightShoulder == null || leftHip == null || rightHip == null || + leftAnkle == null || rightAnkle == null || nose == null) { + evaluationMessages.add(EvaluationMessage("关键点缺失,评估可能不准确。", true)) + return + } + + // 计算左右平均关键点,提高稳定性 + val midShoulder = KeyPoint( + BodyPart.NOSE, + android.graphics.PointF( + (leftShoulder.coordinate.x + rightShoulder.coordinate.x) / 2, + (leftShoulder.coordinate.y + rightShoulder.coordinate.y) / 2 + ), + (leftShoulder.score + rightShoulder.score) / 2 + ) + val midHip = KeyPoint( + BodyPart.NOSE, + android.graphics.PointF( + (leftHip.coordinate.x + rightHip.coordinate.x) / 2, + (leftHip.coordinate.y + rightHip.coordinate.y) / 2 + ), + (leftHip.score + rightHip.score) / 2 + ) + val midAnkle = KeyPoint( + BodyPart.NOSE, + android.graphics.PointF( + (leftAnkle.coordinate.x + rightAnkle.coordinate.x) / 2, + (leftAnkle.coordinate.y + rightAnkle.coordinate.y) / 2 + ), + (leftAnkle.score + rightAnkle.score) / 2 + ) + + // 计算身体直线角度(肩-髋-踝) + val bodyLineAngle = calculateAngle(midShoulder, midHip, midAnkle) + + // 计算头部姿态 (肩-鼻-肩 角度,或直接看鼻子的Y坐标相对肩的Y坐标) + val headY = nose.coordinate.y + val shoulderY = midShoulder.coordinate.y + var headDrop = false + if (headY > shoulderY + 20) { // 假设头部Y坐标显著低于肩部Y坐标视为下垂 + headDrop = true + } + + var frameScore = 0f + var frameEvaluation = "" + var isCorrectPose = true + + // 新增臀部位置检查 + val ankleY = midAnkle.coordinate.y + val hipY = midHip.coordinate.y + + // 计算肩部到踝部的垂直距离 + val verticalDistanceShoulderAnkle = abs(shoulderY - ankleY) + val hipSagThreshold = verticalDistanceShoulderAnkle * HIP_SAG_THRESHOLD_PERCENT + val hipPikeThreshold = verticalDistanceShoulderAnkle * HIP_PIKE_THRESHOLD_PERCENT + + // 假设理想的髋部Y坐标应该在肩部和踝部的Y坐标之间,或者与其中一个大致平齐 + // 更精确地,我们假设身体呈直线时,髋部Y坐标应该与肩部和踝部连线的Y坐标在同一直线上 + // 我们可以计算肩部和踝部连线在髋部X坐标处的Y值 + val expectedHipY = shoulderY + (ankleY - shoulderY) * ((midHip.coordinate.x - midShoulder.coordinate.x) / (midAnkle.coordinate.x - midShoulder.coordinate.x)) + + var hipSag = false + var hipPike = false + + if (hipY > expectedHipY + hipSagThreshold) { // 臀部Y坐标低于预期,表示下沉 + hipSag = true + isCorrectPose = false + evaluationMessages.add(EvaluationMessage("臀部下沉了!收紧核心,将臀部向上抬起,保持身体的平直。", true)) + } else if (hipY < expectedHipY - hipPikeThreshold) { // 臀部Y坐标高于预期,表示过高 + hipPike = true + isCorrectPose = false + evaluationMessages.add(EvaluationMessage("臀部抬得太高了!尝试放低臀部,让身体呈一条直线。", true)) + } + + // 评估身体是否呈直线 (现在只关注角度,垂直位置由上面新的逻辑处理) + if (bodyLineAngle !in HIP_SHOULDER_ANKLE_MIN..HIP_SHOULDER_ANKLE_MAX) { + isCorrectPose = false + evaluationMessages.add(EvaluationMessage("身体不够平直,请调整髋部位置。", true)) + } + + // 评估头部姿态 + if (headDrop) { + isCorrectPose = false + evaluationMessages.add(EvaluationMessage("头部下垂,请保持颈部中立,眼睛看向地面。", true)) + } + + if (isCorrectPose) { + frameEvaluation = "姿态保持良好,身体呈一条直线。" + frameScore = 100f // 正确姿态每帧获得固定分数 + timeInCorrectPoseMs += (currentTimestamp - lastFrameTimestamp) // 累加正确姿态时间 + } else { + frameEvaluation = "请调整姿态,注意身体的直线。" + frameScore = 0f + currentState = PlankState.ERROR // 进入错误姿态状态 + } + + totalScore += frameScore + evaluationMessages.add(EvaluationMessage(frameEvaluation)) + + // 检查状态转换,如果从错误姿态恢复到正确姿态,则重新设置为HOLDING + if (currentState == PlankState.ERROR && isCorrectPose) { + currentState = PlankState.HOLDING + } + } + + override fun getFinalScore(): Float { + // 平板支撑的最终分数更侧重于保持正确姿态的时长 + // 这里简化为正确姿态帧数占总帧数的比例,并四舍五入到10的倍数 + val rawScore = if (frameCount > 0) (totalScore / frameCount) else 0f + val roundedScore = (rawScore / 10.0f).roundToInt() * 10.0f + return roundedScore.coerceIn(0f, 100f) + } + + override fun getFinalEvaluation(finalScore: Float): String { + val uniqueMessages = evaluationMessages.map { it.message }.distinct().joinToString("\n") + val errors = evaluationMessages.filter { it.isError }.map { it.message }.distinct() + + val overallEvaluationBuilder = StringBuilder() + + if (errors.isEmpty()) { + when (finalScore.toInt()) { + in 90..100 -> overallEvaluationBuilder.append("太棒了!你的平板支撑姿态几乎完美无瑕,身体呈一条直线,核心非常稳定!") + in 70..89 -> overallEvaluationBuilder.append("非常不错的平板支撑!姿态基本保持良好,再稍加注意细节就能更完美!") + in 50..69 -> overallEvaluationBuilder.append("平板支撑姿态有进步空间哦!虽然有些地方做得不错,但还需要多练习,让身体更稳定、更平直。") + in 30..49 -> overallEvaluationBuilder.append("本次平板支撑需要更多练习。姿态中存在一些明显的姿态问题,这会影响训练效果。") + else -> overallEvaluationBuilder.append("平板支撑姿态仍需大量改进。请务必仔细对照标准,从基础开始练习,避免受伤。") + } + } else { + overallEvaluationBuilder.append("本次平板支撑分析完成!发现了一些可以改进的地方:\n") + overallEvaluationBuilder.append(errors.joinToString("\n")) + // 对于平板支撑,时间更重要,但目前没有直接累加,可以考虑后期优化 + } + + overallEvaluationBuilder.append("\n\n以下是本次训练的详细分析过程,希望能帮助你更好地理解和改进:\n") + overallEvaluationBuilder.append(uniqueMessages) + + return overallEvaluationBuilder.toString() + } +} \ No newline at end of file diff --git a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/evaluator/PullUpEvaluator.kt b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/evaluator/PullUpEvaluator.kt new file mode 100644 index 0000000..db4289f --- /dev/null +++ b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/evaluator/PullUpEvaluator.kt @@ -0,0 +1,240 @@ +package org.tensorflow.lite.examples.poseestimation.evaluator + +import org.tensorflow.lite.examples.poseestimation.data.KeyPoint +import org.tensorflow.lite.examples.poseestimation.data.BodyPart +import kotlin.math.abs +import kotlin.math.roundToInt + +// 定义引体向上动作的阶段 +enum class PullUpState { + START, // 起始姿态(完全下放) + ASCENT, // 上拉过程 + TOP, // 顶部姿态(下巴过杠) + DESCENT, // 下放过程 + LOCKOUT // 恢复到起始姿态 +} + +class PullUpEvaluator : ExerciseEvaluator { + + private var currentState: PullUpState = PullUpState.START + private var totalScore: Float = 0f + private var frameCount: Int = 0 + private var evaluationMessages: MutableList = mutableListOf() + private var repCount: Int = 0 + private var isRepCounting: Boolean = false + + // 引体向上关键角度阈值 (示例值,需要根据实际模型和标准进行调整) + private val ELBOW_ANGLE_START_MIN = 170f // 起始姿态(手臂伸直,进一步放宽要求) + private val SHOULDER_ELBOW_WRIST_TOP_MAX = 80f // 顶部姿态(手肘弯曲程度,进一步放宽要求) + private val NOSE_BAR_HEIGHT_THRESHOLD = 0f // 鼻子与杠的相对高度(需要通过实际坐标判断) + private val BACK_STRAIGHT_THRESHOLD = 160f // 躯干挺直角度阈值 (肩-髋-膝角度,用于判断背部是否弓起) + + // Helper function to calculate angle between three keypoints (A-B-C) + // B is the vertex of the angle + private fun calculateAngle(A: KeyPoint, B: KeyPoint, C: KeyPoint): Float { + return B.abttPoints(A, B, C) + } + + override fun evaluateFrame(keyPoints: List) { + if (keyPoints.isEmpty()) { + evaluationMessages.add(EvaluationMessage("未检测到关键点,无法评估。", true)) + return + } + + frameCount++ + + // 获取引体向上所需关键点 + val leftShoulder = keyPoints.find { it.bodyPart == BodyPart.LEFT_SHOULDER } + val rightShoulder = keyPoints.find { it.bodyPart == BodyPart.RIGHT_SHOULDER } + val leftElbow = keyPoints.find { it.bodyPart == BodyPart.LEFT_ELBOW } + val rightElbow = keyPoints.find { it.bodyPart == BodyPart.RIGHT_ELBOW } + val leftWrist = keyPoints.find { it.bodyPart == BodyPart.LEFT_WRIST } + val rightWrist = keyPoints.find { it.bodyPart == BodyPart.RIGHT_WRIST } + val nose = keyPoints.find { it.bodyPart == BodyPart.NOSE } + val leftHip = keyPoints.find { it.bodyPart == BodyPart.LEFT_HIP } + val rightHip = keyPoints.find { it.bodyPart == BodyPart.RIGHT_HIP } + val leftKnee = keyPoints.find { it.bodyPart == BodyPart.LEFT_KNEE } + val rightKnee = keyPoints.find { it.bodyPart == BodyPart.RIGHT_KNEE } + + // 确保所有关键点都存在 + if (leftShoulder == null || rightShoulder == null || leftElbow == null || rightElbow == null || + leftWrist == null || rightWrist == null || nose == null || leftHip == null || rightHip == null || + leftKnee == null || rightKnee == null) { + evaluationMessages.add(EvaluationMessage("关键点缺失,评估可能不准确。", true)) + return + } + + // 计算左右平均关键点,提高稳定性 + val midShoulder = KeyPoint( + BodyPart.NOSE, + android.graphics.PointF( + (leftShoulder.coordinate.x + rightShoulder.coordinate.x) / 2, + (leftShoulder.coordinate.y + rightShoulder.coordinate.y) / 2 + ), + (leftShoulder.score + rightShoulder.score) / 2 + ) + val midElbow = KeyPoint( + BodyPart.NOSE, + android.graphics.PointF( + (leftElbow.coordinate.x + rightElbow.coordinate.x) / 2, + (leftElbow.coordinate.y + rightElbow.coordinate.y) / 2 + ), + (leftElbow.score + rightElbow.score) / 2 + ) + val midWrist = KeyPoint( + BodyPart.NOSE, + android.graphics.PointF( + (leftWrist.coordinate.x + rightWrist.coordinate.x) / 2, + (leftWrist.coordinate.y + rightWrist.coordinate.y) / 2 + ), + (leftWrist.score + rightWrist.score) / 2 + ) + val midHip = KeyPoint( + BodyPart.NOSE, + android.graphics.PointF( + (leftHip.coordinate.x + rightHip.coordinate.x) / 2, + (leftHip.coordinate.y + rightHip.coordinate.y) / 2 + ), + (leftHip.score + rightHip.score) / 2 + ) + val midKnee = KeyPoint( + BodyPart.NOSE, + android.graphics.PointF( + (leftKnee.coordinate.x + rightKnee.coordinate.x) / 2, + (leftKnee.coordinate.y + rightKnee.coordinate.y) / 2 + ), + (leftKnee.score + rightKnee.score) / 2 + ) + + // 计算核心角度 + val elbowAngle = calculateAngle(midShoulder, midElbow, midWrist) // 肩-肘-腕 + val torsoAngle = calculateAngle(midShoulder, midHip, midKnee) // 肩-髋-膝,用于评估背部姿态 + + // 假设杠的位置,这里需要根据实际情况调整,或者通过用户输入来设定 + // 这里暂时用一个简化的逻辑:假设杠在肩部上方一定距离,通过鼻子的Y坐标与肩的Y坐标判断 + val isChinOverBar = nose.coordinate.y < midWrist.coordinate.y + 0 // 鼻子的Y坐标不低于手腕Y坐标(大幅放宽) + + var frameScore = 0f + var frameEvaluation = "" + + when (currentState) { + PullUpState.START -> { + // 评估起始姿态 (手臂完全伸直) + if (elbowAngle > ELBOW_ANGLE_START_MIN - 15) { // 进一步放宽手臂伸直要求 + frameEvaluation = "起始姿态良好,手臂基本伸直,准备上拉。" + isRepCounting = false + currentState = PullUpState.ASCENT + frameScore = 250f // 适当提高分数 + } else { + frameEvaluation = "请基本伸直手臂,回到起始位置。" + evaluationMessages.add(EvaluationMessage(frameEvaluation, true)) + } + } + PullUpState.ASCENT -> { + // 评估上拉过程 + if (elbowAngle < ELBOW_ANGLE_START_MIN - 10) { // 手肘开始弯曲,与起始角度保持衔接 + if (torsoAngle > BACK_STRAIGHT_THRESHOLD) { // 检查背部是否挺直 + if (isChinOverBar) { // 下巴过杠 + frameEvaluation = "上拉有力,下巴已过杠,非常棒!" + currentState = PullUpState.TOP + frameScore = 450f // 适当提高分数 + } else { + frameEvaluation = "继续上拉,努力让下巴过杠。" + evaluationMessages.add(EvaluationMessage(frameEvaluation, true)) + } + } else { + frameEvaluation = "上拉时背部有点弓起。请保持背部挺直。" + evaluationMessages.add(EvaluationMessage(frameEvaluation, true)) + } + } else { + frameEvaluation = "继续上拉,感受背部和手臂的发力。" + } + } + PullUpState.TOP -> { + // 评估顶部姿态 (下巴过杠,手肘弯曲) + if (isChinOverBar && elbowAngle < SHOULDER_ELBOW_WRIST_TOP_MAX + 30 && torsoAngle > BACK_STRAIGHT_THRESHOLD) { // 进一步放宽手肘弯曲和躯干角度 + frameEvaluation = "完美!下巴过杠,顶部姿态保持得很好。" + currentState = PullUpState.DESCENT + frameScore = 700f // 适当提高分数 + } else { + if (!isChinOverBar) evaluationMessages.add(EvaluationMessage("顶部锁定不充分,下巴未过杠。", true)) + if (elbowAngle >= SHOULDER_ELBOW_WRIST_TOP_MAX + 30) evaluationMessages.add(EvaluationMessage("手肘弯曲不足,请再用力拉高。", true)) + if (torsoAngle <= BACK_STRAIGHT_THRESHOLD) evaluationMessages.add(EvaluationMessage("顶部姿态背部弓起。", true)) + frameEvaluation = "顶部姿态可以保持更稳定,确保下巴完全过杠。" + evaluationMessages.add(EvaluationMessage(frameEvaluation, true)) + } + } + PullUpState.DESCENT -> { + // 评估下放过程 + if (elbowAngle > SHOULDER_ELBOW_WRIST_TOP_MAX + 10) { // 手肘开始伸直,与顶部角度保持衔接 + if (torsoAngle > BACK_STRAIGHT_THRESHOLD) { // 检查背部是否挺直 + if (elbowAngle > ELBOW_ANGLE_START_MIN - 20) { // 进一步放宽回到起始位置的要求 + frameEvaluation = "下放得很稳,即将完成一次引体向上。" + currentState = PullUpState.LOCKOUT + frameScore = 450f // 适当提高分数 + } else { + frameEvaluation = "继续缓慢下放,控制好身体。" + } + } else { + frameEvaluation = "下放时背部有点弓起。请保持背部挺直。" + evaluationMessages.add(EvaluationMessage(frameEvaluation, true)) + } + } else { + frameEvaluation = "继续下放,感受背部肌肉的拉伸。" + } + } + PullUpState.LOCKOUT -> { + // 评估完全下放(锁定)姿态 + if (elbowAngle > ELBOW_ANGLE_START_MIN - 20 && torsoAngle > BACK_STRAIGHT_THRESHOLD) { // 进一步放宽手臂伸直要求 + frameEvaluation = "完美完成一次引体向上!手臂基本伸直,为下一次做准备。" + if (!isRepCounting) { + repCount++ + isRepCounting = true + evaluationMessages.add(EvaluationMessage("恭喜你,又完成了一次引体向上!目前累计完成了 $repCount 次,继续保持!")) + } + currentState = PullUpState.START // 完成一次后回到起始状态,准备下一次 + frameScore = 250f // 适当提高分数 + } else { + if (elbowAngle < ELBOW_ANGLE_START_MIN - 20) evaluationMessages.add(EvaluationMessage("手臂未完全伸直,请确保回到起始位置。", true)) + if (torsoAngle <= BACK_STRAIGHT_THRESHOLD) evaluationMessages.add(EvaluationMessage("锁定姿态背部弓起。", true)) + frameEvaluation = "请完成锁定:手臂基本伸直,身体保持平直。" + evaluationMessages.add(EvaluationMessage(frameEvaluation, true)) + } + } + } + totalScore += frameScore + evaluationMessages.add(EvaluationMessage(frameEvaluation)) + } + + override fun getFinalScore(): Float { + val rawScore = if (frameCount > 0) totalScore / frameCount else 0f + val roundedScore = (rawScore / 10.0f).roundToInt() * 10.0f + return roundedScore.coerceIn(0f, 100f) + } + + override fun getFinalEvaluation(finalScore: Float): String { + val uniqueMessages = evaluationMessages.map { it.message }.distinct().joinToString("\n") + val errors = evaluationMessages.filter { it.isError }.map { it.message }.distinct() + + val overallEvaluationBuilder = StringBuilder() + + if (errors.isEmpty()) { + when (finalScore.toInt()) { + in 90..100 -> overallEvaluationBuilder.append("太棒了!你的引体向上动作几乎完美无瑕,姿态标准,力量十足!继续保持!") + in 70..89 -> overallEvaluationBuilder.append("非常不错的引体向上!动作基本流畅,姿态也比较到位,再稍加注意细节就能更完美!") + in 50..69 -> overallEvaluationBuilder.append("引体向上动作有进步空间哦!虽然有些地方做得不错,但还需要多练习,让姿态更稳定、发力更集中。") + in 30..49 -> overallEvaluationBuilder.append("本次引体向上需要更多练习。动作中存在一些明显的姿态问题,这会影响训练效果和安全性。") + else -> overallEvaluationBuilder.append("引体向上动作仍需大量改进。请务必仔细对照标准,从基础开始练习,避免受伤。") + } + } else { + overallEvaluationBuilder.append("本次引体向上分析完成!发现了一些可以改进的地方:\n") + overallEvaluationBuilder.append(errors.joinToString("\n")) + overallEvaluationBuilder.append("\n总次数:$repCount") + } + + overallEvaluationBuilder.append("\n\n以下是本次训练的详细分析过程,希望能帮助你更好地理解和改进:\n") + overallEvaluationBuilder.append(uniqueMessages) + + return overallEvaluationBuilder.toString() + } +} \ No newline at end of file diff --git a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/evaluator/PushUpEvaluator.kt b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/evaluator/PushUpEvaluator.kt new file mode 100644 index 0000000..c30510d --- /dev/null +++ b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/evaluator/PushUpEvaluator.kt @@ -0,0 +1,237 @@ +package org.tensorflow.lite.examples.poseestimation.evaluator + +import org.tensorflow.lite.examples.poseestimation.data.KeyPoint +import org.tensorflow.lite.examples.poseestimation.data.BodyPart +import kotlin.math.abs +import kotlin.math.roundToInt + +// 定义俯卧撑动作的阶段 +enum class PushUpState { + START, // 起始姿态(手臂伸直) + DESCENT, // 下降过程 + BOTTOM, // 底部姿态(胸部接近地面) + ASCENT, // 上推过程 + LOCKOUT // 恢复到起始姿态 +} + +class PushUpEvaluator : ExerciseEvaluator { + + private var currentState: PushUpState = PushUpState.START + private var totalScore: Float = 0f + private var frameCount: Int = 0 + private var evaluationMessages: MutableList = mutableListOf() + private var repCount: Int = 0 + private var isRepCounting: Boolean = false + + // 俯卧撑关键角度阈值 (示例值,需要根据实际模型和标准进行调整) + private val ELBOW_ANGLE_START_MIN = 170f // 起始姿态(手臂伸直,放宽要求) + private val ELBOW_ANGLE_BOTTOM_MAX = 100f // 底部姿态(手肘弯曲程度,放宽要求) + private val HIP_SHOULDER_ANKLE_MIN = 160f // 身体直线(髋-肩-踝) + private val HIP_SHOULDER_ANKLE_MAX = 185f // 允许略微的弧度 + + // Helper function to calculate angle between three keypoints (A-B-C) + // B is the vertex of the angle + private fun calculateAngle(A: KeyPoint, B: KeyPoint, C: KeyPoint): Float { + return B.abttPoints(A, B, C) + } + + override fun evaluateFrame(keyPoints: List) { + if (keyPoints.isEmpty()) { + evaluationMessages.add(EvaluationMessage("未检测到关键点,无法评估。", true)) + return + } + + frameCount++ + + // 获取俯卧撑所需关键点 + val leftShoulder = keyPoints.find { it.bodyPart == BodyPart.LEFT_SHOULDER } + val rightShoulder = keyPoints.find { it.bodyPart == BodyPart.RIGHT_SHOULDER } + val leftElbow = keyPoints.find { it.bodyPart == BodyPart.LEFT_ELBOW } + val rightElbow = keyPoints.find { it.bodyPart == BodyPart.RIGHT_ELBOW } + val leftWrist = keyPoints.find { it.bodyPart == BodyPart.LEFT_WRIST } + val rightWrist = keyPoints.find { it.bodyPart == BodyPart.RIGHT_WRIST } + val leftHip = keyPoints.find { it.bodyPart == BodyPart.LEFT_HIP } + val rightHip = keyPoints.find { it.bodyPart == BodyPart.RIGHT_HIP } + val leftAnkle = keyPoints.find { it.bodyPart == BodyPart.LEFT_ANKLE } + val rightAnkle = keyPoints.find { it.bodyPart == BodyPart.RIGHT_ANKLE } + + // 确保所有关键点都存在 + if (leftShoulder == null || rightShoulder == null || leftElbow == null || rightElbow == null || + leftWrist == null || rightWrist == null || leftHip == null || rightHip == null || + leftAnkle == null || rightAnkle == null) { + evaluationMessages.add(EvaluationMessage("关键点缺失,评估可能不准确。", true)) + return + } + + // 计算左右平均关键点,提高稳定性 + val midShoulder = KeyPoint( + BodyPart.NOSE, + android.graphics.PointF( + (leftShoulder.coordinate.x + rightShoulder.coordinate.x) / 2, + (leftShoulder.coordinate.y + rightShoulder.coordinate.y) / 2 + ), + (leftShoulder.score + rightShoulder.score) / 2 + ) + val midElbow = KeyPoint( + BodyPart.NOSE, + android.graphics.PointF( + (leftElbow.coordinate.x + rightElbow.coordinate.x) / 2, + (leftElbow.coordinate.y + rightElbow.coordinate.y) / 2 + ), + (leftElbow.score + rightElbow.score) / 2 + ) + val midWrist = KeyPoint( + BodyPart.NOSE, + android.graphics.PointF( + (leftWrist.coordinate.x + rightWrist.coordinate.x) / 2, + (leftWrist.coordinate.y + rightWrist.coordinate.y) / 2 + ), + (leftWrist.score + rightWrist.score) / 2 + ) + val midHip = KeyPoint( + BodyPart.NOSE, + android.graphics.PointF( + (leftHip.coordinate.x + rightHip.coordinate.x) / 2, + (leftHip.coordinate.y + rightHip.coordinate.y) / 2 + ), + (leftHip.score + rightHip.score) / 2 + ) + val midAnkle = KeyPoint( + BodyPart.NOSE, + android.graphics.PointF( + (leftAnkle.coordinate.x + rightAnkle.coordinate.x) / 2, + (leftAnkle.coordinate.y + rightAnkle.coordinate.y) / 2 + ), + (leftAnkle.score + rightAnkle.score) / 2 + ) + + // 计算核心角度 + val elbowAngle = calculateAngle(midShoulder, midElbow, midWrist) // 肩-肘-腕 + val bodyLineAngle = calculateAngle(midShoulder, midHip, midAnkle) // 肩-髋-踝 + + var frameScore = 0f + var frameEvaluation = "" + var isBodyStraight = true + + // 检查身体是否呈直线 + if (bodyLineAngle !in HIP_SHOULDER_ANKLE_MIN..HIP_SHOULDER_ANKLE_MAX) { + isBodyStraight = false + if (midHip.coordinate.y < midShoulder.coordinate.y) { // 臀部过高 + evaluationMessages.add(EvaluationMessage("臀部抬得太高了,尝试放低臀部,保持身体呈一条直线。", true)) + } else if (midHip.coordinate.y > midAnkle.coordinate.y) { // 臀部下沉 + evaluationMessages.add(EvaluationMessage("臀部下沉了,收紧核心,将臀部向上抬起,保持身体的平直。", true)) + } else { + evaluationMessages.add(EvaluationMessage("身体不够平直,请调整髋部位置。", true)) + } + } + + when (currentState) { + PushUpState.START -> { + // 评估起始姿态 (手臂伸直,身体呈直线) + if (elbowAngle > ELBOW_ANGLE_START_MIN - 10 && isBodyStraight) { // 稍微放宽手臂伸直要求 + frameEvaluation = "起始姿态良好,手臂基本伸直,身体呈一条直线。" + isRepCounting = false + currentState = PushUpState.DESCENT + frameScore = 200f // 提高分数 + } else { + if (elbowAngle < ELBOW_ANGLE_START_MIN - 10) evaluationMessages.add(EvaluationMessage("手臂未完全伸直,请确保回到起始位置。", true)) + if (!isBodyStraight) evaluationMessages.add(EvaluationMessage("身体未保持一条直线,请调整核心,不要塌腰或撅臀。", true)) + frameEvaluation = "请调整至正确起始姿态。" + } + } + PushUpState.DESCENT -> { + // 评估下降过程 + if (elbowAngle < ELBOW_ANGLE_START_MIN - 15) { // 手肘开始弯曲 + if (isBodyStraight) { + frameEvaluation = "下降得很稳,保持身体平直。" + currentState = PushUpState.BOTTOM + frameScore = 400f // 提高分数 + } else { + evaluationMessages.add(EvaluationMessage("下降时身体不够平直,请调整核心,保持身体直线。", true)) + frameEvaluation = "下降中,注意保持身体直线。" + } + } else { + frameEvaluation = "继续下降,感受胸部拉伸。" + } + } + PushUpState.BOTTOM -> { + // 评估底部姿态 (胸部接近地面,手肘弯曲到位,身体呈直线) + if (elbowAngle < ELBOW_ANGLE_BOTTOM_MAX + 10 && isBodyStraight) { // 放宽手肘弯曲 + frameEvaluation = "底部姿态完美,胸部接近地面!" + currentState = PushUpState.ASCENT + frameScore = 600f // 提高分数 + } else { + if (elbowAngle > ELBOW_ANGLE_BOTTOM_MAX + 10) evaluationMessages.add(EvaluationMessage("下降不够深,请尝试让胸部更接近地面。", true)) + if (!isBodyStraight) evaluationMessages.add(EvaluationMessage("底部姿态身体不直,请调整。", true)) + frameEvaluation = "底部姿态可改进。" + } + } + PushUpState.ASCENT -> { + // 评估上推过程 + if (elbowAngle > ELBOW_ANGLE_BOTTOM_MAX + 15) { // 手肘开始伸直 + if (isBodyStraight) { + frameEvaluation = "上推有力,身体保持平直。" + currentState = PushUpState.LOCKOUT + frameScore = 400f // 提高分数 + } else { + evaluationMessages.add(EvaluationMessage("上推时身体不够平直,请调整核心,保持身体直线。", true)) + frameEvaluation = "上推中,注意保持身体直线。" + } + } else { + frameEvaluation = "继续上推,感受胸部和手臂发力。" + } + } + PushUpState.LOCKOUT -> { + // 评估完全推起(锁定)姿态 + if (elbowAngle > ELBOW_ANGLE_START_MIN - 15 && isBodyStraight) { // 稍微放宽手臂伸直要求 + frameEvaluation = "完美完成一次俯卧撑!手臂基本伸直,身体呈一条直线。" + if (!isRepCounting) { + repCount++ + isRepCounting = true + evaluationMessages.add(EvaluationMessage("恭喜你,又完成了一次俯卧撑!目前累计完成了 $repCount 次,继续保持!")) + } + currentState = PushUpState.START // 完成一次后回到起始状态,准备下一次 + frameScore = 200f // 提高分数 + } else { + if (elbowAngle < ELBOW_ANGLE_START_MIN - 15) evaluationMessages.add(EvaluationMessage("手臂未完全伸直,请确保回到起始位置。", true)) + if (!isBodyStraight) evaluationMessages.add(EvaluationMessage("锁定姿态身体不直,请调整。", true)) + frameEvaluation = "请完成锁定:手臂基本伸直,身体保持平直。" + } + } + } + totalScore += frameScore + evaluationMessages.add(EvaluationMessage(frameEvaluation)) + } + + override fun getFinalScore(): Float { + val rawScore = if (frameCount > 0) totalScore / frameCount else 0f + val roundedScore = (rawScore / 10.0f).roundToInt() * 10.0f + return roundedScore.coerceIn(0f, 100f) + } + + override fun getFinalEvaluation(finalScore: Float): String { + val uniqueMessages = evaluationMessages.map { it.message }.distinct().joinToString("\n") + val errors = evaluationMessages.filter { it.isError }.map { it.message }.distinct() + + val overallEvaluationBuilder = StringBuilder() + + if (errors.isEmpty()) { + when (finalScore.toInt()) { + in 90..100 -> overallEvaluationBuilder.append("太棒了!你的俯卧撑动作几乎完美无瑕,姿态标准,力量十足!继续保持!") + in 70..89 -> overallEvaluationBuilder.append("非常不错的俯卧撑!动作基本流畅,姿态也比较到位,再稍加注意细节就能更完美!") + in 50..69 -> overallEvaluationBuilder.append("俯卧撑动作有进步空间哦!虽然有些地方做得不错,但还需要多练习,让姿态更稳定、发力更集中。") + in 30..49 -> overallEvaluationBuilder.append("本次俯卧撑需要更多练习。动作中存在一些明显的姿态问题,这会影响训练效果和安全性。") + else -> overallEvaluationBuilder.append("俯卧撑动作仍需大量改进。请务必仔细对照标准,从基础开始练习,避免受伤。") + } + } else { + overallEvaluationBuilder.append("本次俯卧撑分析完成!发现了一些可以改进的地方:\n") + overallEvaluationBuilder.append(errors.joinToString("\n")) + overallEvaluationBuilder.append("\n总次数:$repCount") + } + + overallEvaluationBuilder.append("\n\n以下是本次训练的详细分析过程,希望能帮助你更好地理解和改进:\n") + overallEvaluationBuilder.append(uniqueMessages) + + return overallEvaluationBuilder.toString() + } +} \ No newline at end of file diff --git a/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/evaluator/SquatEvaluator.kt b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/evaluator/SquatEvaluator.kt new file mode 100644 index 0000000..5137b0f --- /dev/null +++ b/android/app/src/main/java/org/tensorflow/lite/examples/poseestimation/evaluator/SquatEvaluator.kt @@ -0,0 +1,218 @@ +package org.tensorflow.lite.examples.poseestimation.evaluator + +import org.tensorflow.lite.examples.poseestimation.data.KeyPoint +import org.tensorflow.lite.examples.poseestimation.data.BodyPart +import kotlin.math.abs +import kotlin.math.roundToInt + +// 定义深蹲动作的阶段 +enum class SquatState { + START, // 起始姿态(站立) + DESCENT, // 下蹲过程 + BOTTOM, // 底部姿态(深蹲最低点) + ASCENT, // 起身过程 + LOCKOUT // 锁定(完成站立)姿态 +} + +class SquatEvaluator : ExerciseEvaluator { + + private var currentState: SquatState = SquatState.START + private var totalScore: Float = 0f + private var frameCount: Int = 0 + private var evaluationMessages: MutableList = mutableListOf() + private var repCount: Int = 0 + private var isRepCounting: Boolean = false + + // 深蹲关键角度阈值 (示例值,需要根据实际模型和标准进行调整) + private val HIP_ANGLE_START_MAX = 175f // 站立时髋关节角度上限 + private val KNEE_ANGLE_START_MAX = 175f // 站立时膝关节角度上限 + private val TORSO_ANGLE_START_MAX = 15f // 站立时躯干与垂直方向最大夹角(微前倾) + + private val HIP_ANGLE_BOTTOM_MIN = 60f // 深蹲底部髋关节最小角度(深蹲深度) + private val KNEE_ANGLE_BOTTOM_MIN = 70f // 深蹲底部膝关节最小角度(深蹲深度) + private val TORSO_ANGLE_BOTTOM_MAX = 45f // 深蹲底部躯干与垂直方向最大夹角(通常会前倾) + private val BACK_STRAIGHT_THRESHOLD = 160f // 躯干挺直角度阈值 (肩-髋-膝角度,用于判断背部是否弓起) + + // Helper function to calculate angle between three keypoints (A-B-C) + // B is the vertex of the angle + private fun calculateAngle(A: KeyPoint, B: KeyPoint, C: KeyPoint): Float { + return B.abttPoints(A, B, C) + } + + override fun evaluateFrame(keyPoints: List) { + if (keyPoints.isEmpty()) { + evaluationMessages.add(EvaluationMessage("未检测到关键点,无法评估。", true)) + return + } + + frameCount++ + + // 获取深蹲所需关键点 + val leftShoulder = keyPoints.find { it.bodyPart == BodyPart.LEFT_SHOULDER } + val rightShoulder = keyPoints.find { it.bodyPart == BodyPart.RIGHT_SHOULDER } + val leftHip = keyPoints.find { it.bodyPart == BodyPart.LEFT_HIP } + val rightHip = keyPoints.find { it.bodyPart == BodyPart.RIGHT_HIP } + val leftKnee = keyPoints.find { it.bodyPart == BodyPart.LEFT_KNEE } + val rightKnee = keyPoints.find { it.bodyPart == BodyPart.RIGHT_KNEE } + val leftAnkle = keyPoints.find { it.bodyPart == BodyPart.LEFT_ANKLE } + val rightAnkle = keyPoints.find { it.bodyPart == BodyPart.RIGHT_ANKLE } + + // 确保所有关键点都存在,如果缺失,则跳过此帧或给出警告 + if (leftShoulder == null || rightShoulder == null || leftHip == null || rightHip == null || + leftKnee == null || rightKnee == null || leftAnkle == null || rightAnkle == null) { + evaluationMessages.add(EvaluationMessage("关键点缺失,评估可能不准确。", true)) + return + } + + // 计算左右平均关键点,提高稳定性 + val midShoulder = KeyPoint( + BodyPart.NOSE, // 使用 NOSE 作为占位符 + android.graphics.PointF( + (leftShoulder.coordinate.x + rightShoulder.coordinate.x) / 2, + (leftShoulder.coordinate.y + rightShoulder.coordinate.y) / 2 + ), + (leftShoulder.score + rightShoulder.score) / 2 + ) + val midHip = KeyPoint( + BodyPart.NOSE, // 同样,使用 NOSE 作为占位符 + android.graphics.PointF( + (leftHip.coordinate.x + rightHip.coordinate.x) / 2, + (leftHip.coordinate.y + rightHip.coordinate.y) / 2 + ), + (leftHip.score + rightHip.score) / 2 + ) + val midKnee = KeyPoint( + BodyPart.NOSE, // 同样,使用 NOSE 作为占位符 + android.graphics.PointF( + (leftKnee.coordinate.x + rightKnee.coordinate.x) / 2, + (leftKnee.coordinate.y + rightKnee.coordinate.y) / 2 + ), + (leftKnee.score + rightKnee.score) / 2 + ) + val midAnkle = KeyPoint( + BodyPart.NOSE, // 同样,使用 NOSE 作为占位符 + android.graphics.PointF( + (leftAnkle.coordinate.x + rightAnkle.coordinate.x) / 2, + (leftAnkle.coordinate.y + rightAnkle.coordinate.y) / 2 + ), + (leftAnkle.score + rightAnkle.score) / 2 + ) + + // 计算核心角度 + val hipAngle = calculateAngle(midShoulder, midHip, midKnee) // 肩-髋-膝 + val kneeAngle = calculateAngle(midHip, midKnee, midAnkle) // 髋-膝-踝 + + // 躯干角度:通过肩和髋的y坐标差与x坐标差的反正切,并转换为相对于垂直线的角度 + val dxTorso = midHip.coordinate.x - midShoulder.coordinate.x + val dyTorso = midHip.coordinate.y - midShoulder.coordinate.y + val torsoAngleVertical = Math.toDegrees(kotlin.math.atan2(dxTorso.toDouble(), dyTorso.toDouble())).toFloat() + val realTorsoAngle = abs(torsoAngleVertical) // 简化处理,取绝对值 + + var frameScore = 0f + var frameEvaluation = "" + + when (currentState) { + SquatState.START -> { + if (hipAngle > HIP_ANGLE_START_MAX - 5 && kneeAngle > KNEE_ANGLE_START_MAX - 5 && realTorsoAngle < TORSO_ANGLE_START_MAX + 5) { + frameEvaluation = "起始姿态良好,准备下蹲。" + isRepCounting = false + currentState = SquatState.DESCENT + frameScore = 300f + } else { + frameEvaluation = "请调整至正确起始姿态:站直,核心收紧。" + evaluationMessages.add(EvaluationMessage(frameEvaluation, true)) + } + } + SquatState.DESCENT -> { + if (hipAngle < HIP_ANGLE_START_MAX && kneeAngle < KNEE_ANGLE_START_MAX) { + if (realTorsoAngle < TORSO_ANGLE_BOTTOM_MAX + 15) { // 下降过程中背部不应过度前倾 + frameEvaluation = "下降得很稳,保持背部挺直。" + currentState = SquatState.BOTTOM + frameScore = 300f + } else { + frameEvaluation = "下蹲时背部有点弓起或过度前倾。" + evaluationMessages.add(EvaluationMessage(frameEvaluation, true)) + } + } else { + frameEvaluation = "继续下蹲,感受臀腿发力。" + } + } + SquatState.BOTTOM -> { + if (hipAngle >= HIP_ANGLE_BOTTOM_MIN && kneeAngle >= KNEE_ANGLE_BOTTOM_MIN && realTorsoAngle <= TORSO_ANGLE_BOTTOM_MAX) { + frameEvaluation = "底部姿态非常棒,深蹲深度足够!" + currentState = SquatState.ASCENT + frameScore = 450f + } else { + frameEvaluation = "底部姿态可改进。" + if (hipAngle < HIP_ANGLE_BOTTOM_MIN) evaluationMessages.add(EvaluationMessage("深蹲深度不足,请尝试蹲得更深。", true)) + if (kneeAngle < KNEE_ANGLE_BOTTOM_MIN) evaluationMessages.add(EvaluationMessage("膝盖弯曲不足。", true)) + if (realTorsoAngle > TORSO_ANGLE_BOTTOM_MAX) evaluationMessages.add(EvaluationMessage("背部过度前倾或弓起。", true)) + } + } + SquatState.ASCENT -> { + if (hipAngle > HIP_ANGLE_BOTTOM_MIN && kneeAngle > KNEE_ANGLE_BOTTOM_MIN) { + if (realTorsoAngle < TORSO_ANGLE_START_MAX + 15) { // 上升过程中背部不应过度前倾 + frameEvaluation = "起身有力,保持背部挺直。" + currentState = SquatState.LOCKOUT + frameScore = 300f + } else { + frameEvaluation = "起身时背部有点弓起或过度前倾。" + evaluationMessages.add(EvaluationMessage(frameEvaluation, true)) + } + } else { + frameEvaluation = "继续起身,将身体推回起始位置。" + } + } + SquatState.LOCKOUT -> { + if (hipAngle > HIP_ANGLE_START_MAX - 5 && kneeAngle > KNEE_ANGLE_START_MAX - 5 && realTorsoAngle < TORSO_ANGLE_START_MAX + 5) { + frameEvaluation = "完美完成一次深蹲!姿态非常棒!" + if (!isRepCounting) { + repCount++ + isRepCounting = true + evaluationMessages.add(EvaluationMessage("恭喜你,又完成了一次深蹲!目前累计完成了 $repCount 次,继续保持!")) + } + currentState = SquatState.START + frameScore = 150f + } else { + frameEvaluation = "请完成锁定:髋部和膝盖完全伸展,全身收紧。" + if (hipAngle < HIP_ANGLE_START_MAX - 5 || kneeAngle < KNEE_ANGLE_START_MAX - 5) evaluationMessages.add(EvaluationMessage("顶部锁定还不够充分哦,髋部和膝盖可以再伸展一点。", true)) + if (realTorsoAngle > TORSO_ANGLE_START_MAX + 5) evaluationMessages.add(EvaluationMessage("注意!在锁定阶段,身体可能有点过度后仰。保持核心收紧,稳稳地完成动作。", true)) + } + } + } + totalScore += frameScore + evaluationMessages.add(EvaluationMessage(frameEvaluation)) + } + + override fun getFinalScore(): Float { + val rawScore = if (frameCount > 0) totalScore / frameCount else 0f + val roundedScore = (rawScore / 10.0f).roundToInt() * 10.0f + return roundedScore.coerceIn(0f, 100f) + } + + override fun getFinalEvaluation(finalScore: Float): String { + val uniqueMessages = evaluationMessages.map { it.message }.distinct().joinToString("\n") + val errors = evaluationMessages.filter { it.isError }.map { it.message }.distinct() + + val overallEvaluationBuilder = StringBuilder() + + if (errors.isEmpty()) { + when (finalScore.toInt()) { + in 90..100 -> overallEvaluationBuilder.append("太棒了!你的深蹲动作几乎完美无瑕,姿态标准,力量十足!继续保持!") + in 70..89 -> overallEvaluationBuilder.append("非常不错的深蹲!动作基本流畅,姿态也比较到位,再稍加注意细节就能更完美!") + in 50..69 -> overallEvaluationBuilder.append("深蹲动作有进步空间哦!虽然有些地方做得不错,但还需要多练习,让姿态更稳定、发力更集中。") + in 30..49 -> overallEvaluationBuilder.append("本次深蹲需要更多练习。动作中存在一些明显的姿态问题,这会影响训练效果和安全性。") + else -> overallEvaluationBuilder.append("深蹲动作仍需大量改进。请务必仔细对照标准,从基础开始练习,避免受伤。") + } + } else { + overallEvaluationBuilder.append("本次深蹲分析完成!发现了一些可以改进的地方:\n") + overallEvaluationBuilder.append(errors.joinToString("\n")) + overallEvaluationBuilder.append("\n总次数:$repCount") + } + + overallEvaluationBuilder.append("\n\n以下是本次训练的详细分析过程,希望能帮助你更好地理解和改进:\n") + overallEvaluationBuilder.append(uniqueMessages) + + return overallEvaluationBuilder.toString() + } +} \ No newline at end of file diff --git a/android/app/src/main/res/layout/activity_edit_profile.xml b/android/app/src/main/res/layout/activity_edit_profile.xml index 6710178..9a78fe3 100644 --- a/android/app/src/main/res/layout/activity_edit_profile.xml +++ b/android/app/src/main/res/layout/activity_edit_profile.xml @@ -119,6 +119,113 @@ android:layout_marginTop="8dp" android:background="#333" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/layout/activity_main_tab.xml b/android/app/src/main/res/layout/activity_main_tab.xml index da339d4..f13cff6 100644 --- a/android/app/src/main/res/layout/activity_main_tab.xml +++ b/android/app/src/main/res/layout/activity_main_tab.xml @@ -3,13 +3,40 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" + xmlns:app="http://schemas.android.com/apk/res-auto" tools:context=".MainTabActivity"> + + + + + + + + + + + android:layout_marginBottom="56dp" + app:layout_behavior="@string/appbar_scrolling_view_behavior"/> + + + + +