From 3f4b82301d5db9456357cc42730fbde7412d0793 Mon Sep 17 00:00:00 2001 From: maxluli Date: Wed, 31 May 2023 14:41:59 +0200 Subject: [PATCH] Added a lot to the doc and I need to sleep --- docs/Images/Screens/BattlesWindowExemple.png | Bin 0 -> 33020 bytes .../Images/Screens/OvertakesWindowExemple.png | Bin 0 -> 21899 bytes .../Screens/SlowestAndFastestExemple.png | Bin 0 -> 44874 bytes .../Images/Screens/SmallDriverInfoExemple.png | Bin 0 -> 7792 bytes .../Screens/SmallDriverInfoFullExemple.png | Bin 0 -> 36979 bytes docs/index.md | 153 ++++- temp_annexes/Code/ConfigurationTool.md | 198 +++++++ temp_annexes/Code/DriverData.md | 107 ++++ temp_annexes/Code/DriverDrsWindow.md | 103 ++++ temp_annexes/Code/DriverGapToLeaderWindow.md | 37 ++ temp_annexes/Code/DriverLapTimeWindow.md | 37 ++ temp_annexes/Code/DriverNameWindow.md | 63 ++ temp_annexes/Code/DriverPositionWindow.md | 47 ++ temp_annexes/Code/DriverSectorWindow.md | 37 ++ temp_annexes/Code/DriverTyresWindow.md | 146 +++++ temp_annexes/Code/F1TVEmulator.md | 293 ++++++++++ temp_annexes/Code/Form1.md | 32 ++ temp_annexes/Code/OcrImage.md | 544 ++++++++++++++++++ temp_annexes/Code/Program.md | 27 + temp_annexes/Code/Reader.md | 235 ++++++++ temp_annexes/Code/Settings.md | 420 ++++++++++++++ temp_annexes/Code/Window.md | 322 +++++++++++ temp_annexes/Code/Zone.md | 242 ++++++++ temp_annexes/Code/recoverCookiesCSV.md | 88 +++ 24 files changed, 3130 insertions(+), 1 deletion(-) create mode 100644 docs/Images/Screens/BattlesWindowExemple.png create mode 100644 docs/Images/Screens/OvertakesWindowExemple.png create mode 100644 docs/Images/Screens/SlowestAndFastestExemple.png create mode 100644 docs/Images/Screens/SmallDriverInfoExemple.png create mode 100644 docs/Images/Screens/SmallDriverInfoFullExemple.png create mode 100644 temp_annexes/Code/ConfigurationTool.md create mode 100644 temp_annexes/Code/DriverData.md create mode 100644 temp_annexes/Code/DriverDrsWindow.md create mode 100644 temp_annexes/Code/DriverGapToLeaderWindow.md create mode 100644 temp_annexes/Code/DriverLapTimeWindow.md create mode 100644 temp_annexes/Code/DriverNameWindow.md create mode 100644 temp_annexes/Code/DriverPositionWindow.md create mode 100644 temp_annexes/Code/DriverSectorWindow.md create mode 100644 temp_annexes/Code/DriverTyresWindow.md create mode 100644 temp_annexes/Code/F1TVEmulator.md create mode 100644 temp_annexes/Code/Form1.md create mode 100644 temp_annexes/Code/OcrImage.md create mode 100644 temp_annexes/Code/Program.md create mode 100644 temp_annexes/Code/Reader.md create mode 100644 temp_annexes/Code/Settings.md create mode 100644 temp_annexes/Code/Window.md create mode 100644 temp_annexes/Code/Zone.md create mode 100644 temp_annexes/Code/recoverCookiesCSV.md diff --git a/docs/Images/Screens/BattlesWindowExemple.png b/docs/Images/Screens/BattlesWindowExemple.png new file mode 100644 index 0000000000000000000000000000000000000000..feb2ecfb0edb81ad28a1ed097588085215e85f23 GIT binary patch literal 33020 zcmce;WmsEXwC`IgP>QvsK=D$F7N@wiK!M`!E}^(f&{Eu80t8yzp}14r2@)hgph&O~ zT!UTSzWeUyobzEn&)MhP`yp9bGUqerTyu`G*7%Ko!j%=J@t%=Ad+^`^-WM53)dvqA z);xHCf%_Em{>W^iDE<8g!$nnE{6WPS#rFNqW6MtppB_B;6^nCi^5lLW+fhcx<-voO zuK#>6mH^bA4<3Ar|04NG-O~^S$MT`D1m7J(&I&Yi%Dqxq2iwjY`?p1CuqodCbouRs zMS-QYbsich^k+*tbX?AB^<;zp*V>XDEG5z_RdezT?GLD{w$QWxV%xfL9s=wY;)V#< zl!ATRxsOMp?3Y@B*lmWf&&8k81U=t=Ll&JN^Dg0T#sG*&#t!X{_qjiMf2t?HqO>&f z{>*iQ{d9@OOF-~HM}5h9J*=S_s?H)F z2AlN5-f@Mujaezsum3hk%KMQoz-{YPG%*Md4JbVC|<=vj6B42rQiqd+w)1Qhr77%io_}V!o|S{oq57 zTB6dhyp%l#@QJc^j-M>k_%#sF5CD*&sD zR`SZH8d^82zTw8)5}+w=<5hf*X3&q*8l4G87~1R zE)z)9aa(p9DKEE}%p;9ruT3Bn=-SIwEPy+T)G%n3av9}1eDpr#hj}h$0)YcbA zV#}lO;r_((*)F^9o-8T2AYHGRes$Qof1Q)=1;wq$N#1j7UQ#Bep?+N#qK3t*Sf9(` zyS|R=wFT`=%QsVbX2$zCws83GvG3j&ld0<9eWSrlvWCYxe{Wp}r^Cfh?9ad^ZF$TS z0k$IssPbe}3Or+`+8v^Te;Tp`Z8J4vUi9+CFwM&Su)8!eU&ns9daQK(r}D0AUc$H5 z{e05xE8l{VQDp%tcUdIhsM@rITNUTuCGAf*MmqwCGf?e@CcoDy4tdXz7LI>1Ifv-~ z<7ylk5*8vJ)Qa&v;CZJy)4_|FZ%n1%owzq^e)NTRzkK&f)JZ?2H!j^M7#0^B5LoF_ z1eZ8hPK&Ew{1&V1PY!q^^c zhz3st?%JSeR~fa|=qadYQ@^+aqAuc?TPj}|K`zx-WQ*pn+Bb|x|!VYD;t?h0aRH= zxzV#bP+9DMmmZ9~@qs)}8mH;6i9q6ybq7Vk0~f_ps-HA-6=vB^WEJI}OsnlDTfWKct#KC|22k289{&N#MAitcO%dK)?Q0zqWO<$7SChF zuK8=E-`agiBl`QxO0KKye>J=b9Pj_uHJS3iSyhAebV*S6icFhu&C-%#dPas?Wzchy z^iJb9Vq(-_#Z)rdpvG}D8vPVAuvkkx3`b%3r+q_4W@aWdtp>Tf`|f4cd6czJl}$hByCg7fYE+}zyUyOYL&TB%8Mt`+AdRkQo{$a;8q9PO@UVAo8F|NlHd z|J7Lk;}rjKmtH^s3WfT>!=s|An((*(t%mIP9T`L^RHojH)lAa;rpW#ACSriYT)Ah!jb@N`10x7F5L&88RLf>0x7`Lo3N8e!S)3vbIhbQFQl#}@d zVCr0hwvJs!An74A&E!`chEaB~eI>d9&DfVW)43O=8b{KbM3zrlQ)XCR!z1gY@~V`k zv#Gos!=cj4zYTM2+pI~_&E4IU3*^|O|Mw>3x3rLh6{Ve=I4>_Ri+@=b+7-sjyt{O` zpKkg&??$p1hy3W;2X(zZhFmFG5V~9+B1{DC2e8cC zIa4hA5n(Ek%)9uGG?k>@U6>OD@8p|!+E(u(JIMO)tSthI<6}4k61^|csVzk=y;b>A z*`P!ovEem{*dRd7T3+ye+V}JxYX<2^xVfE@i~?MadSPFqk-vXG0D(Y%yGciZPl$_| zxR6o*Gm?7%FQFwb((ln;?IFV$;bpHo~GW(vsm zO5*%vsdwKVf$}(WM&&&P&Gh_oo4}Nl3d(7wNlD4zpR~`*z00Hg@#SU_ zU2iYybNuEr$Vt8=%Fh+1t5HAFGZ9~%!dQR(V7BZip>U~1fM*|sg`==Qu?w@!=h3he zJi7FGjf-hp?-F?+9>zDee_pzbPpMd#=pxcsGEqO}>2rabd~XTZR9*rg8c8pSR_#5N z8lC}7T&4BPfNZH)nwG~qQVf;ubVZCfDmrA{uQfi|HGs$7^eA~Si!z0kJm8x`i0Dm*>5Re$%*| zn^ltMfTW@3E3??d(Uvjpk)7$G<{NzvX?D3GyvJva7lYLL-N+m~tWnqPoRRb&GdxDp zkWcU7l=Q-bfyDFVcr{yNRQ8R|VPAM@4Kj`{PbFZjwNk!Px;m$%W9j_*^;+*2iibj6 z--%11^=xhZMIzm3-M}Ag=PmP9NPY_PDMIS?4ZZ0S@uZ_JYD=b){uu`^H~S72SOs;izNy)GMm&}|Qp-eGT*{UM&F`Puqsak4 zH+U?GR5=dU5eSvtr&G|}lvI6V-o0u;08930lpqOu@6-2yD(ilZMFLNISK2$xFz)1=qZ#d!0ziQO{Mo zg~IkL*KQ>Q-tNLc~&;g~tqS>hy-03D{yNlxGXkrf1DPIGI>IBZq;5?MqFyVIokf>$o zdYD?s9Xk7&JYD|e^Uo?4nET!^A{C@-hN?{Ri1MWh`{E9 zI8;0JWa#dHxF3bps#N&`_nk8HD-A8J(h%qBRnZ(jYSB|)dYIb6m%()%`SSNa!(N3t8xykg&sZ+v6AYkL&z>U}fvHB9qpJt5b8t1g=jDo^6Y6AyKlJTpt`X z0T?jx*o%acEgWFdN}6HIpUFFy7IGekcsCi?wYpf!156D_5lvoRI|LJ@x;AZ73gPvw zHp_hmas^xhb*-eOPb6(4DPqe(Xi@&dp z+tB&!t6=HHfTF!d{&%t9Km6a@GQt8$t%y9(-#=n{;o^DW+G!KKc>Sxzg9&uc`_2(xJL z9UMYN7)pN;`R9GR&TlxVu-SOG(7VpzBI;71J%l%0z-c%uelww%uJ{zPD(?Bdt4v?R zIlS2LmTk|cAJsZ~$zlY&^S=D!d+wA?c8e&q@r3)bi`~w>>?6V3mtR-XAE~3hrZCE^ ziL|^uH@ZyY-7oYTn$6~Qy0Js7Z|Eirw;(A!94%*Du4l29J$!&-K#*ILHSn$~D19{^ zocLg3>p3OmR9cHCg$7Uc)LXW1Lo^#p3q6p#$^fOx2o+Ew**Mvo}*Tepiu!*Jo3K@}n@#N zLHpIyyYm_^zpH#Id%ZMaz8uXjJh!9nXH-1nTclKO_N!^5CIAJ3ghadb-=abpGdgNh zF*u%4AMD*GU(66Xe;ybMb7D(1Nm4rCw3&FLLqpCkvi+k5<%7nE?9jo)8S&%u7~@X- zg)H`r=N} zW^;M$k&RG(C;JiQ_SKt;(i^|(wsR9EfFj$79X9dl#T~Nl=N_#KH}XHksHa8&K4y3v zJcVCr9(RY5S@E%&R_hu{Etg(z3qc$iUkTK=j355H{if1_-G9$@0511ccp<%Z* zMicsVaq~KApbQ~l!?1=Z_a7>FUPO#_IB`pvj37lKQI`{I=@EJFL^OWvG*-b>&-k|h zQ6d1|C5gM0Y^IM|V_rEFH zz0u8ZwNym4=pswUSeL2p11kJ0e zOs+^_wM9`V8OB6MV6QQbdaT$l58{LhZcjE-edX@^F6krk!Q;tOL{cf@GKLl6-*q$( zJ>sp&cgfVvBGO_W>B|uf(aqijp)f{*3AH~sJk-q+X;L9tos9ck#FfER!LY$OMXYRM zyDlfHJ2&v-nTM|*k)Z-MOS9xrz4{U@sjXLISfy`GQsoV7Y(){ppz`Dk(bG{W%4Q6g zqpph1^**N2_vxG?a5-@Pt z6(kS*Ryk{J&*&}}t=42K9J3c&nek?b3`TD`_yzPg^bAsp1vgFgJD~|b&bucJ2O-{r zRP4H|Zm_9fGoO_hBYeP9p+PV?8L6y{5p7AHp_|&$S%$q==rBnkD?7;Vue@=|*`HIU zql#M+>8CHE-1G+H5uYJdt@ZeI!ifQE@PfLOig0*kS?)xt0sv)OKvcpWk*DLt6uA>t zYAjIHzHWw4L>02Vnt8#$Jz!Eb)feE#M&-$viZ!FF*a}Xr?<}%F*XAa7n*fG6+;5L6}d+ls1@+P z`hGtxY#RO+BiZ#$OCc)e27}5{*&C-VMxB%DQ;6O71!Nuc7~phrAXV#bJ8DDd z-1nXwY1rAKt}da)`i#8Aq?IDi)(K|j8gcbkdrsg4J#R;2c$+nrhWp55 z_oEQ+tgrt-z&Jlso8FVZG5lw`P=@uUL?h_2=5@`i; z>L1KvF{s6gFE<$lT0bW>h>eFd;MZ{0n?>PI{hbB^*O!){T~X_Gak%ow7Manyf$o!% z*D>@$r{om78f$dJ5y#UfXjG2occwdKBJa2GJigk z%q{L3#;UC?0w{-*BF2-;6z**7@Y1&GM@onD6>q>B&VW&;()>)Q=8i_o4x{nv5M7iU zMUKlXv7umQI{jqCKa=fh>83W#R(q&4t=KU8qJOX-0Pfl)HF$jUB3&s9|85IsjXW12)Cv&6BJeQnI5s3Ei)J{%dbO%= zu7unSQKDFF=qMKxy|%=;^GqhF;qI!v!aure_wbpo^qc(6w~wu=#jk0E;gtF88iZkg zdHqS$#fbfVBrCK-`?tSe*|$v*FE2Hhw$oK@RvPn_9Y?aPrwW*Ti?t z+*C?*PyOEdytibocy`Ao#~je(1crbKPq3ATN4T>+O=g{FNg}LBZ1bfIaA6qq2Jk_D zzdOc;+0TcSObBTs_O;Ru*U7vy48;Yxk1bX2*IY^5dnb^zuhvT;4rL;1FDf`vf^YOS z$O?ZBWmf+L2XGr>IdrCPSCV3OG$_kuwb}AWTo-MYVQd6PZwf&Xs6%E zWUA8;bJ~ZdBh=3*Eh=0~h0lsvi$!-}waHDMEn=eDj39IYy^W%lWrKkAmn1jzIjeX_$M5%T~+keM)P!FxNRrGSLij*T$Q&!AH;u*N@&tj)#Z>pPcfi~yBaZk zmD=qKSThm1#UO;kH`W4aWbJjnA*MOg4cRt8teR((3JOThiOp%s4y+xWUMA40+4cIH zC)l*nHrxWjd6rx2M*^P!EGJz4;<#qsQb)vwqcrd|-_g3UYFIDL4l$mJ)znn|#NY%k zA($as8uC67!VOJJM^a=&Q zC{byFwsJ~1?#mw3DT>_E)EGr}hWn9&SOr7i<6C=G)Ew8)5!yHK52jvzl6>H$q*duG8(&0gKZZQJ5WwkWCvB_7Z^+e}RrgRGls9NEn@;>~W%iKpC6Fm(25C{MYCjTiBGinG06i74N|X zVFv#nyqM;%DlJvWrwiHs>Cr-ZR%8cUbKYUmrF<@uqnlbSMyOEt>dRz1Xlb;Hccb{E zF}cW<5h8xmX!Ww+$kM@v;E^D;;k2f`mw52f*z={vdaB~iuj+Tx!@&?M$$w(tJx%m0 zXJ_u|>FJD2Okbj3i0d=XgW_&)Z`aQXX6>o`n$rH5AQa#}m+5a7{DkB8J8aiS7c*L& zfmpN4u)$@8zyPhGi?M7josMN0XKDqUfEdOadj|yNEIua=R>qxj-<@~g2jQC!&Nf&& zuKOytPM?D=&P=I1dwK&ab+*WXuj539F7ibdJ>>{Il*WJEHB9>)GvTz%D;|3=A0+x) zFmzo02wlt#zTP@#^E=xGjcsPjIB3%NdXZ$`hKg=%fo*+Qve~aKcJYZAxGg2qE|)Mx zJ>BAl0`6bK!4MB>r*YI1RHw{5A?%1Azxt$tSG4(qEM&^!`fP%QYQfD^nTMEwb3dKx zc1_}uIVIbw-d$pMN5&3N-=hK(`==-j?r}oMH(vucdu9)S$D7l6E zNpot!e&w`qu{P*rZnMtmv_#ZCD=EDQwAi0qiUw|GJ`VUu3=uXhIClMX#%f&O3-b5n zn+_hYpB>vc-6S^WS@{tVt+e4J)5c(ZmFBB}MV(&Bbt{v?cCOlx6AIBMqMu41?5oo} z+ZR0zv(dB)s}>a73yWC-pZ#WUVABdhT*xTpl^CvCq3lg+=Jiwv{<+OGzAQlL zu*%qidOxj1eE;@g`{lPlu(U=T_q(bMOwf52y=d*CQhx8i$L#RE_+xO))5|86kv39W z@4WB(!e4V}e+b>6ipLHI%F5>;7a9iFZC=!*^_b)uhJ6Iu#VxnsnA?0(&m95MjMdw7 zcL$z+M{&{R(P*`16Llk4=c$9kCb$mM{jNG@U)0Pb)%_82IPRa}SdFKx5f3{dRR2^1 zA8Pj^kM?!o8nDjsLdTjP>;wk;9Ah(}4xd`A`N(ILg@j|!r>4WMv}ydCmwUMvq6YoS zb>pUAbJzM+T4#Onqu1&gs46JvaSgg+-Bm{Tp|yE4gzB(P@>7cOlD*LNh@w=gWSTSH@kE8qyj;tt0V@{)g*<3MXiOcdf95bQL7m#`8R3F+m@HD->&R z)Q8s94M^qK=?r#cJ`VKMX-4CP_BtkTL~ zs}aQ*`62R_n5&x}e^v-q4G~v6FYbToD#epc7WGIIALUZ5FxMMos7e zmh{Yud|)7@9tDN%2TJ6#=cF7{OJ+GWM!r%K8!E?wuxnj>h)CL~d!5S)xy$C;0E1v1 z34Pysyqz{Nhg_IPbIYeZv8IfG=Ohq4C%#A9fHysKJJqOvHj2%pPn`~B_#XX>9{Cjh zE2j0o$q)aJtJ>Bq*G+chrzlRfoLO2@l#qA?s1e|idg}DEXYFv^Urttb9BI*^78aRb zQy>CS`P4QlPKz3+8~I{DRru zU-+L~5^?Iay}zG&f8FN-o`iXw7ypAXoY;1~N0_oWO#eLq`+q!u{}mRVEZ41LUUB4C zWrvjA6WW3TdRbZ7zeF`+7Mb7CA=cEkeOkQ?)RYo>RlX{6&T4XYAnNUq^N++F5< zJHeeIB2+_G;Bw4hZ>sW%fD)Otwh#-p2!JJm_#K|?U$zJ|P7kj*B&Sw9%Fw<-UIE)F zAO2b&P_s}l{my)~Oy{>*=n$Cdk3@5dZ*KM?GH^zo)a013T}1xZTz&E|=Uuy89#QW~8nr zTc)G}kTVzvT+hdBSWo~h#IUYd8V!Fe8P`d(RrY4}fICz2Rc4N^r#nYTv8&@rQ4+^QfGwLY4dV1F)P z6BNO~RFut!sa2T3c=((*c`O-i_B1VSa4)9BCeS{jxR{T4Gtl@?Y1kM$ma}cwG8Gv% zy&N@i?FtVs4jc;qllu`@lnAi2Ua>8;_)v@m+(7pihmd;b%Kir&r-7Og@2!hU9Zg~h z939UwgiU1fWMfA|1k8lF_u4}}311aylz>wy#~8~_1rMj=X`D1Lb%FQnaoK_{B+J*@ zH^%EVw5d2l2<#+)(VV`pwMW@QEn?_?8|~o~sagNV*;+>(2IPL7O8KLum`7)(Fj>Lu zpX>q8`#0nQTula2poMxe@w?bRjc^!$Ip)bHu}(8Aktl62+mUPp(`eWVj#PM`RJvvP zic3Nv&yv^lS1iIU7J*mM?1dcck8D_^KD^%0_BI=%Z+L^H6+#Lw`IxGwXyt?veAz8s ztN1z-AdhBT`k3CJY4x#dw~14bo(s6dyw~DE9Q>1N$LNB`|9Z;kVze7_yc1I1I=}s+ zx;CyW!N(TO(MP*5H$h3`9CA?9@M@((zc=*Ogp}v(4OlO0wHV8W|S7_ecAO7k*4yA%f4|sb!;++NtW2Yh{=@4 zy)r~N#%AA+gI4Q40cJHM-}KLm8f%cE@Q( zRyiHFJXx31T9=0sl}D>&b)pNpU-9OOvUH?%ES^v-Nm<&wXBE|$`O&417%kb=-zcum zx+&ScF+wm9#3b1j^g1Xdvq~3=E{vsUrq@+l$#?C7UUR~@23R$gm&0Pyo)Pw^Q-Q4= zGoQRFb*Jyn^Xq?>$#4zGSb;#Cu+_UU2bkhOZ- zjQk>k=+$Q0_-80Dh349>&MQrXF~zgzk)%}sL|pm2y!V`(j9pAO&ueuUi$;CIst?~K zFn6I&pNF3#gjQ1G#VY-KO%DO0-pf}}wE@;bZZN6JnkSL)&lCL918J zPVq)b5(T3lyGYD0BpdlD@2PR|$YEsKRR!c2<=+%4WZx0JV6WFXVoE#Vn=!}7QSDUH z=lIjw{1&lxJq)Fq*+=G05y~sl!1NBDEnP)t6w%2z#?F6yU%8g;cW5DcP>=n})qS$= zo(8Ufey)4%-HyBIi*bh@#E71F_$9OZ>>9M*891o9*__6b0RV|a$W;WqUp5j)@_u?b zjT(-%mR2GPR$rqjJO0>o=t1Vu?JMEGIBM-zicEwBfEpugoQam(tA`5Zk7*0VDEAmE zbNA}6Jo|fCxClhLvZ=jOjLNOPC}=bzw{aJ+%@s_Y>LQ&;!L23rod6SeGBZm(DG-?$`&oO$^i#$3dBOVGL@Znc1l+vZyX(E8Xu=WIsZAOiljeV~sJTrIbn8aeB3V<>Ul}jEtaxRiS1~mpO`ya0P zyqm`K+b!)_KQA~xt~{?Wo(O7%a&^CZ33v6FK0?R@}RZF^A~2ChJ+2z^irXa7jS{_5DsUS8I|qM;nG4Un*hE_aUthBQ(7mnHKUuIsHH03f2X|M9I~P+0{*0XI6Gb;m3?ib4QKx_G_Q~*To&>KKb7Tlx z62(3zz~_7=@7RoN-gOrOfk=DSIE8)T*6>gQyKPo{iYL=h=nUu2oCghRdUDbAKWMyk zW5(y^KJ}Bvu4cU}*sz1ksm(qxsMj4TWZN)^JgUl26T7=iWCZ{E-cq>mtFC>lK39Z- z3_EY9ZM<1JnKEy zu)Vef^()=wkpj>eX%7dnN0g!J|w<0 ziyBK~KC%Q6JqUAUOv#RQ!AAd6Z~R|v?F5iLT_U8vU<8L-3Y)C8Z(xPRWw zRk?ptIkHfI&485rZX}+Gt@pZgIRZL=Bv5NW%kiwq`ihOr(iJygrpPo-8%23#RQ~dBv?2qMEo(p1$He>E$ z{QR==?vnTJw2+lz?=?@Ldqi7DY=GnX2O&pM@s&?#be$9{&knAu7j4SoGv9Lv*DC(any)9#nUf~N4D zN!=Et=?m}^a^kWo?5b2M36tcWjxP4ENRc~%L2GcqI@H<&@>$fXOl9EC`xI^_H59&F zb@SFbZO3Cbly7qX9r97@COq4$?v||La=qhdpSm#}WSxG7^}hA!8NIIWyEFSuP~U^Y zK80Ne)-KIBX$?E)7nbf97Wlh!-Q2hNi&e_8$Pcr}O#s8rlVS^bJ2Z}%K1r^^JQK|l zN1#aLWX4_KohJluk?Z}Z-1#y8o9TW5j#t8vTYrb~ykMw&`tQYG&1X7Czf#Iq=bsQq z^a-|cJ7Lu6!hR{{mQ<&xYPh#2zZDoo^Szb-;2D(Ke)C22Y6V(HG=H!};JEBzXxjaA z?Plhbp0;%Q&Je8}%8nOH0ASw)XE|+~{77v_|y$pHnaT-ziNWWG}Y-$m~6~Jccl&qPysZmPozhr`y;5op@$lPj@wu(nwN?-U z9?o`bTrC5m6s&ii@DI4Yt$DQJ_f4tQjHqEWqz^@K^oU4Ul;h>cy(<=@>*Ye21EgRN zyu4yCJ0^LP8O40%Cz{1lnrqcBQJ8X*qRA_le8YRLFBBO}YZ3fAqEor^WUzOAtTh0+ zd`DLYzDvu#Szm7QE{XiC#bVsGzWpk1gNn29?l~S3zpCLHGo^iB(>{xu&ax;O4KF5T z`<5jk{S{j>d`A8wVq*PCPQOJ-y@#k8gVzMZL)@r|t3o>@n|nF@`>n{`?=o8w+SKsy zNQti9`~pwRWVKCa4TXnoY+;3V4=M>vr6>jU9u}0D8@k1H+Ch%z*!>{g1Y&t1Vh3_4 zUJ+K(IffB)lW25z7Tu`SHL8{h-XozGnI>W;>^4m+l3Hu%pQz)9^cgDit+ibmh*4xe z>>0?n6whoiwtR%eCtTj1OF&I+xR@#IOo(&2z1W^kk}W8Tn&{77Z32s&ctO@4GDO3o zXc({>6jOZ$kj2$i3%mS~&EJuo4+Pk%N9g-R$5(%wj`bvS`*@FXBIM1++&r9@ulCnV z!k@f$zfKanK{s3<4&v=vZ7VF%{OPjSA!bQYB`((b$b&p&M#?g(4v&l6}CeAU+w|Dj$(O;Y5I|@ zKwB$0WiAg=x}TW%<^BoMR6uj+0SCku*=fdXvR5lU-I#@%D~tX8$Ank(OfXP!+7@BX zz^9+cnpl0w=Q3SQDo>^*XMqydEOu*j%Tt0TyQGQS+NOfAIa%g-C;&N?nHEgoed825-T}5pIiK zH~Ix6!2;L2-DNXNo7~n|hcXTG57{xv>0L-`3W(wp5rqm*#^IIwg&GQ^O&Vo0*^M2! zSUbC|n^`z%^**h?^Uc;-Av{h!o$^%&XkcQ%^5 z7B0jp!i|Y=8?9J0r6>R8vZV(Q+cJsEWpWU*Mr~Sk^*lqRkI8SrA*8ZB@!WMg~p za{JS6zK6Wtu~SC2f<77sc~J$Bi)0yRO$8fwwLO% zQVB{qY+{`}Z|Li(twgt_MnYj7-sCOrZZB$V#Bc*$dFrR8D#>)-N{ai|?um(SepKeU z?xuT+*Oe8j;rZ2gc*)&s;p*x9Xwn#Y=r6G%-o8V5ZO$E84Lfat4A|PckqEiZe1Q`4 zpS3lk`h12;P;(T8w`Cx?X9G?aiQDIdwstwJRGTLxaDwMg&&FXEZ-^m16b)lPIO^9h)55F<4)JonRqRV*3ZzLNC;` ze`tuacvvUN(fHe5uJrcV=L98~pp;PHChA9dUE-N2wV=fJw{l^=>}`GcJQcJ&3j@Jw zE%o+hcc1FaY+Mq`xKV` zcB-5fs?~6?H0+mjU9mw{WbG8QYpG_rY$lSBWPV}6%Gx?7FYn!7Zt+!V5xKvwWbfd0 zV^O=T<3ah}zBPm05IlO8z1UA+L~4AWeL7FB>P7d?)hH7)q6_75EnD*dg@IUKJqRtKLIu$bKk3Nr}C)KOFKm;Y!Hv<(DiDEX8n zd*8zKeHTQoyL}#^lzam2&VN(Y^OSgTmkfZ>yzOWC0BpGUi((8%ahMoQUcMovj80plvJh8VM>}50fm02G%9GcSi z16dsTYOXB(*CAr?J`A3{Y`@b_#2Zh(0@wM3&oac0dzfOmhx$Q6`tBxK%vUZNXABU> zqx8JQu`xw?dP25T{R#S>;2Ag^?DJ6jukwh2I$uo0PAFP3#6xC%pzQhEavl^MQje#% zRcEo*zQeuP<|BM8t47GGn08LH*90JzvkBSy3LiaY9t>9E&vY$mugB*VzIn*{lOqOr z?l_%1+-PNLodcLGFKa!LUE-9M{@oF1Ni8I4rt)+r-TxsyBc)EMcO;14Ee`l(j zElc#&tRo!N((Q?<53SJdo!)N+HCYcGw)a}7k2a>n`fM7EKM)bYm zQzw()Zjy0eb;dc$eWN5NRzA5R(OPy$PN) zJ!*<>rDM@)O{3FdF*h*~ZJs%`^}FTFnp^DsXnN6A8dcIXq74)}Uuq1avv@J@)pp|D z-IHt5b&FvdtX9M}R9O2acC{p2KD|7p$=tKiRF-Z}hWje}j)Fi=>yGGXxGYNG z0)f*AkRnC&Dc;BM|1yo97fvt383W-uNi#&M>V`7yr9oV$h6SbQN}2NhRl1rqk@7Ts zUv52W&xBM%xLmjKH%a|u)L@QeGAkRqvPC3QIfa2cXCw&(aCY)iA5C8`QZ!UCJ(Pv+ zsddc}Ifff^7}ppmdQ3lGom@Y^O0>(I+FsZ9e)hE%^?{07l!L}ZGfWO@K@N}z#C4-W z{O*+a;Ju_Drx1aD`sC*1;^bYKPLkHNs*uSuLV9^)aRVMgS@Bv2*bv$>Yc*M60mkQe zjsFnjCI|AKMzvlIh0AkLZcM{jYfmmhuQr}sZ6ED~lT)q}X>mCn)vj1rOEd*I4rB0& z+)%K0T))+8S$mw=Q4Cs7QB=kN(zq#dSL8e!>MtCM5{S|Y3?G|ypMow*{P+=4l6-#H zNxsQJTN+x>M_*61Cs%=F8elI-V07hK921F?69J)^jKgT3(F-Sn3$T%4RPnOwKyf%-`wVDfw@fH(2NT7u5UCm ze(>57XpPo#uP>moDh>Y4ak~ND-R1l-g&gF;RJ+IA_t{?Kfx@?in>w?lM<>4waR9rp zrgg1XrswD5%R*dt+aYZCPad1^TlgopJ1rqyAfr#o-H;z8-os1XyAC3wQ?e&g1I4D! z?Jv5GX|c{`kI}bT(4L9fWaLlazWZ6jVRD)O?DW!-eEV0e+hO&e z|55PYmdgHLB(?v8>h?bA2%lOs3U06TA6WZ?KvGiDkJ(|r!sjG%mX-|hGLIvB|5cTK z?|j3X5xu%XH9HQYQa*hZ|m^QTh@;l=k#!y0Z&+SCRdXYTnh{lnA$Q6Td(U;!VC+}8T-sZ-uego( z8Q;46{suf5imAeUx{f=MOf3^VUWE3+=hAa_3{t-dhi-kCg~7rH&ASlJ`F-~|H)(Ir z-c^vYx3_nFbF;873tSJDdfP3g8M3pp!=666u^w47K{gu@fWgJht@c+J%}+u98kfF- z&rX8G(m3j+OUZ%Jq_hK2?2$ctJB!#Y^f~W#SPA7r)?FR(%|^2yblfem0bPDx!{{yc z|EIdQjB2y}zdft)AO%{ALnx&{i@Qq;6k4>nyOrP;9NOXWM$oV?(53^-FtsFZI>7@mBB8$_?@cZV^OGnAt%qa zlRJ?Wqnwr?j~uFoCk)TVXN&Fdcgbe^%IOjUVC?vU??az?*DlNypNcz!$*_kHhw3$^ zpm{Dxh%q`~G^VrS0gzunMuFe%o=MK7ebm*~8*MRy-i0X0NI8fWxxK@vePT13(?9zul!8Lo_H_@v8b(udTj@P>?2}LT4wrhN2HW8ASJ+M(N$SscGN&N@1zx)6D z*rC>Lck~$jURx&@Si_eNofT$WQJCSnDy^)sR{POnYzxURz~k+_&o3_#ItcYfwuRY@ zC6&Md03r1-6=jLWGz+UZiS+(w zBKqbo(*1Z;mSigHkicu-BqH(lm66hw#=X7%H=X#Y9)onHx|-5vz0?865?X?y>e?3S zGCVo~t3m8g%C4FCxiE7yk<1Lqvk^ad-DU)jwW+Hsp#TXeZIq8J^jtSl zW~o^vm&#<-flA z24l%)6TJ-mTu@^fcR%lmfgRoojeX~YDx>4sQ+9`yg9e6<0FH(=^%-FA^N&B&1_{ZJ zM-3KaY*Zdt_=;#fp%M-w>>U88S;^SEo-G;xWoVur*B0^(d@|yb*!K4#1!croH8dk; zu32U6HZBCVZ+f)U!Sc$Rd64^nn(i1kePTZJtoId%Kq$OJp9`b=<~O>BbL6VD{kWoXzdP z<;+1~ioU(pRT(^ZcIvPbBdO?L|6{x$Duo9cz9}QMtT$;-M7Oh(8PYEr?1t<6;aF#l$;m zVmwA#yE?&67D)SLWiPf%tn=$4X^fhb0M*UZV3cZ^;rF>CP1)L*L91ItcanZ@2G_}L z>2=yrE<{{`g3}D$h0knVP{@?(vTD6tp7sINuvVG|VKEqtW-O)RUQ-|kSC}gNNG}yH zV$HevWcgjk_=98J!*mG3bl{hfUc{_zJE#SlmMm0K#z((s zFXfIXM&F`qoCzV70?#yIM`w@+2H6jrRUN39$yzfQa#@dUMY#59&5NsJdP)%d+E)oh z{jj~4)_6;vxZ&H%mdC;_rv~6KHDzSD)o5sEUeC_5>?ALv+}`tMZNUt78WBZ3-)ejN zhSE1lbJV0$Bst$fX+?r#x_rOhg0@LY$Op>w=_g6X`Zsueixgq%Y4CR6lT} zhgy-OkMOB`269RJI)>N04pIlrrLFG_UJI_x@wegYd*g0K1RFi*_#?P>QKn40hbtfO zm~Y|q%|Z=-&`gepc%E{gIUN||!RH`%Kjg8BeQIMv6n-D9*h?J9H`-Bcoc!@!LDc72 zP@PnQ{;dpgO<_Q|WA-KIhHjOC?+mm@s$6+?G)Bl*d{AdN|1HNmyX=AW#SYkXh2?uQ zv5I1Ez|^|ItZJJiScK0?enb7G*=fa4ypAkt_2YAoQZt)uw80REwR-<%Ed-Be6Ff%h zTxHp9si2vrHCt}pv;ErqaKLI+4(G$UcoF7Gkhx5Y`uAp1vfdFgPaJ0bkcJ4v=XF3f zC0?Hkt2m z3mJz}@Sv=IarSFPbBExq5sn+6!-QlWw+o%HJ!dkHAW=_`mULvQ>5b;`+kS9jOhy~T ztc138Goa)!V77p2JvQ@{lCbhWUJdyt9ahC-j(+;F2;WVV;PgJ(s$UU6VA6K*sKi!( zRC6#IVg>yUu}}H^0o6W{_#`?}i>tQUr>qo|&nMlrmQzt0mi2(lSYEwQp|Fab>%Lv$ z=dMr`^c#CXsKTV*k9l`H8E+|g$h()JOFt!Ivy5LJ z%KbBo+_3y4_TVX*AvpV)s9Z&i&eti*>8BpWBic6-wW@02H3Z{})yZ^Rv`!jRyY>Zd ze)njJ#E?y^_RiP+*yGAaXHJeBb`AR4oyFLcBos9 zFJW7fEpS{Eq(`cR<0@Wxm!3OL!s1zhsDzx&6AkiVb62wmi-k7d*XSqKeXS+ZiEH`X zr!Kp@Uzwe9*u3mClGfXM#jH#%ph>1ydiqG!lCiBev7{#QkE3t+hB46kSBj#Z@*7r) zEYr&R^dv_4zBErZ*!#wQ3AG`VB{|Y%=Nm2BVAd9MNH}t>?fTq;E4%yqHzRwmbuGa? zTdhB54UBeK7V#OftUv05%SYLr~EQkDxne9d|}3$tFZ{hczm!N*`pXA zcQvwsZ%wOoO)5_XA#rw}3~Vxj^&U%e6qk%O6<-F`Xf2P zAf0<~P#LmBGkhW}T30i#kWW6moueyQUL6sw z4C_JRe&df%W`f`M80{nTh$(sGmxJ+rH&f=629eBZwPp%z958>vh6lmyJPv9sY92FM zZ$ZT4$$6c~OjYgdlc9Px#ZBzU*aMGjSW6cShCQ`2GcL;>sNUXCorK;|6Og-S=MBQjxkff(?s@(U2+P%E z4dXlx-U(kC5$8c(O2Fm`s!DB68-)_|g`FQZIyIw`oW|1lh8Q_f z=|o{ycV<`7#U$OaYrvWOaoYb&+uh~J#l?vHlRFnKjFEa)8q0efWn;%TD+30o*2Z`l z8c*I$&X<9g?ADOIgJ3BYRZ^K|p+B0}tc5CwoTd)~=nFDoo8cAxEVGS;03y%Ab$>S0 z9%W6&^L_L{z#>VW?-9}LdpBru?7saW0n$JrM32m^M5-YuEStFMuyr;?Gcn9i@HJ;j z3R?dgs~515RbX3|bx7_6{ceer^0KBMrGp5b2BuW9@KJpRk2Sr1#7}RHdz;-ND2U`4Gb~mV zRxT-h`r>vMnLnn99qLOzniN$CC);32hlDt-v{nH3R8U zb11f&8-98C0Pi^BMV2`ILMds2Z)Hp7?Xskw8Jpgd$Cky;frYQr32R*VVgu|Bi$R|z zXHCW;jH=I12oiWGJF*2Ll#ZSnS(?7>;*!G7<}uoxMdzoaVl!jOY1f6H zeIw@WeSZgDEQ0L@@9tr)er;T=B{@NnhC3 zA8-J{vVE&vRyR=2R?v%D`k!O+#V-T=9Aa7ydZ)t?8OkTY`n7{yU5l|x1f}}684F&1 z#XGrTTyZVW77Wlzv#To;X@#|;#?P27MJ_&VEb^8q%JQT3-t9P&L>X`sS*wk%$~J?` z{XfyrTDn`Ui2m*=$s|A!k&N#PK*@x@^i!EbPJA1pe(C7&r?lHSN5xYr0L{*) z=u1EMCy%#|a7Wixmjx+>8-o@a#{y(tp8KFbE7$ct!Qz2(CukTF?b-baJuoXLZQ^~5 zfv9&-rtDpOm<;7vjz`$u7X6FQ)xRrkUL^Y%UA`K+g*{ivDHN68qXE^}7!WKu2Y*J& z3@$w6ukWo=Ht4^`ryE{kgMT+?;Cgt%RbAEprOZc^kHyl$X|}6y{gI4?Vagxve|*tf zjx#+|=ls@)%z?zixz#5Pwb1vl*=)@bqxNWC0;&bZ-OoBZUg|gzk3v7sUq3RCxM&yz zrt&$K9wjax7A$`U;fn{1|I7@f7m)}UKi5z}WfSa;`;)~^zEqjX7FF39gnavbQOKT9 zH>4RjrBNJLV$@J@D%@MDu_JXUH<x&zz0Tv9Pr(Admk>l zw+ccfEX`1=dwl*XsAU)~^b$Gy&eM%ep3kPnPn*nYAG!A;hKNukL34JaC(}bgD(xuW z=-$+?sb0WJNiSe+>mks9D{~l9pz%5e8aH1VRcOkb`XfPC^nG*NyGVFq?zlpsrd7UN zxZ>CA_3Iy&N_xK60HRrDx5;m`t{Hh14WqHU`&LJ2 zA8{GrH1Dp>o#GCP4xG>;d=y=4!EId~2`zyrdemj>_U8Y2Y-up!?4n&sYBC|02%bRdFo`(oiYQ;bqq(OKng}6iz&?OY^and#8N_VH(@!6j^$dS@NLSiM%v#u7_fe(C1}1 z*Q}@gKc78cj0W68D#-2yJkSHSfTB2dKBxw@w>fF+0Mr{D9cFare z=Ucb%EQU)tq@v@}@>U9_XMCMGDgda?G@IE77E()KmIZ;jM@bxXQbc^ zx;uO2qV4)?wxln&z!^49$r&_HT-nYOrNf%QhMsKD zvMHi!eG9P9nFfEQaz39RG?}r&`)p!P_Q3(YSErsJr!g3X$_>`?QvQ4fQa0Wda5dAB zwvr)xru8B^=7*RT3hP%W1?8kLc$cpz4Rb8eFG6a+R2cEEzoF91jzF(xNE|B(%iesy zBn_1z_1V;xx~Ja&pq>5|?+v`(71SS4Y0;lUmPz--;wkC+`cby!eQCLUv^2-??NZo> ztQLmNFn)f9x3@67#(uFmeX;DRu{TV7mrn1uPj0;~TFJs&V(5%}#>{n(2GDv@wbKYn z`?}w0cRrYW8=vjTwLU-`;NW$67Z8bQ;p-_s*}f`~_{w0$SmbwKC}3&B?@GSJ%I~bk zZ{L6LyAhi3k741wpzHmak&7*GVBhq9X7{nHZ?%zfofg^$$maW-3gj@?yWZ!wf7O9d za`#0YZQQNZtL`qpMf8Hp7Dw(JB;DqMO8qaS4P7`EUVA^7F_5zZ4p~w(@`U2x5j|Ow zIOTuVo%X(PB6RS1e%^c#Pkl6@%$H)PFIlA4nlwKJ9yarZNl)D4ka$W+MWw5?L$iZv zC!G=$$JM(4v0uJ!jScwjk3ygts%!H&87_VOf+xlIdBq|axB^t;D*IJHE)06ibUpFf z;!lJ6OkMr`7+^%?T3|jJAqjWFU_4tLW$?0da(vlBkcDO^fNJ-r=dWJVw>!c49J){; z0vYE|lVZZ#5$tIBIC<-&1VVW7LXTq$>mTFrfY#)w!BafbRKVq6lZZa`tD=CHkLl#> zof^`WzM1OLz_l{nQ3&DK=)79gxB0;34KfbLyu4hnu>cqjiypvXE1ZzL&;IUJZ|ASB z&f3+@s#@(&qlqC z(xeAH^x_v;(<}s}Lr0@!+q;v1q3W{mIU-!vyCQ+VC+FRsC8UnMx$=wBq&2N?@jG#8 zajca;^B-+bW}?Z$=xt?rSCxn&m+E+h; z#0r+hrT_yc8#2TV8C3;ieW;EJN2iNmHOplO+T!<>u$oEoYrL8b=A_Ayx84+GYXQrx zN^@aR&K{^ROg6-W)q`l1r##BI9!LvEt?p@mRSZ!nOW)h-atQd=y~jUA^bBv-rEYb_ zvAbN8$JB}IrQ7HilvDd|O?=6G)8KMm+l#O5MIdq?P5%;Cw10QY1>jOag0QiJQcTl% z8ZanUoGGc|^l#8>A(Gp!xV-!}7v<+jR|4ztFg5)$q||6pOhTdmU*br;kh@*2;tzal zyghfnzR0|h66qq9=HItZCVEe{k3N!&9Ip%>kn$AYD>}*cxcswmez0MudWKIDQ}VOm z1fzMM8}(lE37)~1pLYL%4G?ydm%iHxOWY`o0Pxcw@-h)8K@}%T*Ibqk*lI6Od@!9| zk>sHLu7xH0nmHXmPrpqMHWyzFx&6Y(Er+^Yv1-;r^{iNt7XC9vOQvwta=wf9*#t`n z--jt>3P=wDUi~Iqs_!17k$F^IyXHeRKx6rHMxNV|rA8z<10V)!dvoDlzDh@o(*sb# zTC;{2{FoW3IM!BSV*Q7@mBH9#hmG+p37kyEs|M*?ReV`{_VY>OL$63&s&PQ0Se(ql zQz4a#rxWSt2F9#@lJq!1_tPC#oiniF;h(FUe!<}_ch_=U9RV~5;z2g{0jnZo)A6CG zVtid=+*Apb-LFCvmI=#p$2=12viujTP}3v|r-geD9|Qa7&0mOxN`CG^WhsJu1*vkD zUO*#kjUKRy+Cs)-l^`U-rah1l;l13_6g)d|Y1xpmpZJC78>0lQqD)K1N-9T-9i0lT zC9F+zUMg_#>uGby@9;st&tD`e&uBGpI^|UrCI^I_{(3+#oD;w4(6u}r>&>jmP4ui^ zeMyU;vVRT2okqn(UTd-E33c9kCG$fpdF6tqDEVzHKGgc*RF*hz3o8;V4pSni=_$s) zen@C5Zx$s7C-w{vSMlFRXOf{K`!M4JMu0_)zlx{0)B6;7^so^=!>zWh>Tosvn>_2l zSbU4Z)v>XqcaH~U$-SGQuggc6HWFE)2*WCtyuo3HF`U3Iq@mo!{j-C4idYT}8s0up zeTDv0^+VqBK6lV==mUk*rhd&MN;!_{-=1+}y6+K! zWN2Ka4Nr&^ELx*@gxG}qB1Xj0b2fHTKwP*F$eQElCQ^Wav>t6IVv)~IbeYH4(EhkP z>NFMpNnQS_Ow#Kri?ENdF9qKU0?%FQSb913+Mn>aC042-=qDZV1hJv_N5HJ`G#1wm z(sDvd8DMHo&78)G-yp=2Mkov~mGqT4+My2VtYCtcJsf_h=KIR~{#unMway@RlpY5A znM&oX(q<^LNoKNAV+#f!E#Pfmgdg8lOxr0F1R)qf(w3=B; zN=o2#7i&BDmwmlpcKW;hKWuD-CGTG^!pjNjkJ%&1K1^70Hdd(R3P_pUa{pI+e=o-S z>aXEQ>hd3Ck+a>GriT}66CRtROLdv>P~4`$a&ZQ0wNa@&5Zp&EEp=YDva?T4j2EP z3E7V+62yhV%w%XFHuUxN?3l;SFJ-I^7?#DkhDxsX4oHsZDR;YOPgoL`UVBdLIk(X3 zG3aMm#B7VMZ_qnak|cKGpBQy~L9M7viJohwbz3?*n-`ZDbYf@%gyfn|ZK_YFAB)4LrDc-wQyB#GAG=H^O5R>ciIogS zWRhUvRtd?0X}#G28Hhq;O1uvR$I-!pXO<@BtCBIi?F-9UiI14ZNukJpuYUrXIy!-4 z_5gpvmE+p~?5jgV|EBPqI3;)4$qxp8Q)PlT&F`=TbeTiFO zH~B(;)M>)IAq&2kt>uT%p5kc%LyUNO!VP3WyKI4fY8&y(*xli;aed%)W#!g#EYz} zTcCepJYJooydDa&)!1|Ql=^2zj@dFtC+WU~F?At5C^Ywk&yEfMX zbl08ZB=cNa2gzYI-mcL5fslo^yCR};jT38kk(oaqysM2Xw4JoSL_ijZ=fw;$35#sY zRmpU_K8*7soD8m(*i}tFcBqi4jAzJZcu$DCy`Wcg=KTm>(OP6lT5EK=l$VeYhe1y@ zlDmdAJ>ypZ(OJAWBvG$9_(EsC9bRuFAU;A!!Q2LJE&oSOM<5F0PczxBoh!)|)ET&w z=rYu_?&EmfY|v1 zDJm9bpmcl!$r)67#i&##CR=d1HSo`jUd`g$*FKjc$h4w$BXp+(^!oX!Rn@6C*|+Rr z(?vBte}7=Ag!O|<;q|!}FxJ=WA++{(yrVonySs%!HgQ^*oq}qqs6DQYLl&|F(3J&y zF@gEki0~a_jWrpo9d3g~Blx2=TbN+e#gOFpVY z4IC>gJ7U@v5#RvtrS5RaSd%O_TC-^B?Un&s9gTCINWvDGCY<5*P#+cpc3QQQ+tqgL z{EXw6Pecnd#w@V->i}59hBc4GCvsUX{SZ3LsyFg1<$!n`KRff7SW-qLBr0D-7nHVa z0UgWn)fai+zVK`LF-E1difBtWaBRt%EjK7hstNCoV*ZYDOL>?5W3JyV1#VBiCUN2p z#hiRtbd)!Ktl@O|k5l$DoeS&m@~@_@E!b>dUCY`E6()LYa!%e=O3RSMsXJazWX^5O zmcrjsoyQJ9+WX^^`+$>`U}FJkD$I{uppK;J!gIqN7Fyqx`7C%lJi+OeG8a5Eqp>43A0_H&^LE&Vf;{XJ;HC96oa zdod}4q@YH8&P9<+vA4J1Tf>_{rTzRKY$C`K7eVX8lssK$Ac9W7hwz*%3gYevSyncW zg-fzXpVX3YCJfjDt6`shz;nUP`!ITZisLz_NqyE{DJS)O{7 z8uDbPYHfH^Ku?bu~{6TIjauH1G-E_E?1blxM>+77-uc+N%3^Q`_k_fBYiRqk-C z*;(gxc3PqGU5}Hei`U!7K^<-%aRPjZoK_1={DPbc2kw9;+I6+SiCVX?Ful7llS2Xr zH1TZ*2J60`Q)Vj}K=jzUstcPD4LUW(lU>G?UUY)b%LNzjB>J3;_gUIlA4cIfNM~q& z#gEeg9Yug)E*nF_7gT=((}^A5xPPpGZz|;$J zEl!d5s-}Iz+w_U9cX>p0f>~E1rA$JyYg%-EyQB!eOQRt6G!nY3pCO0ZY?(4R;-UXg ziJ0}Qv2e$-qp3TW@HKuU{QB)SinPw(F^rA1mZg-M9Be6hckGZV-rOT6e^z{=JY|<- z_i55D8c^YBNvb-9)zb_i#%+*#qmVm(jQI;rZa9^U^_wH7K$dzH*w4kI^FJV^C#w99 zwuF+FRS@MaiIZxp124po-L1Qg?2zHYuGGtC==l`p#a|o(@Yj;rffI`anbt zF776^EU7_ff z-zV>*Kq0dda~@C}X$OnnV#Sz_txBSe17jzN9Se;R>2r3weyKXf^Ib>jzO_0w1ru95 zuaYHN{DSJPk?p0V$H!06zF&;?#Y?S8smiGmr=o3qhUEfo*1~4SW(MN~vnwK6Z=e@# zVc>C)c^lRs`}1`wm5U+DCs&P+j)R1D$Imo5BIo`?|0GHr&lGd4{M_sxu@sg1@~J?V zTr6u(rGDP?)h+JLp<~dN7HTz^)SK{8v$8wHs{2s7K`X_7g08ZbF&%v=3p*L(tW=0m z4OPI!O^>M+4oz`>PjMIEmO^V|=+K6zG>Kgd#s;OhB1{)C<_HNS zV(*%=O|BITZtKs7=5@GLn-_;a^0F$F7}tGCZLW9g+F{aFlhcbO?QNYe1@}&>ekDo% z^7E^1ed<(0Y!z`AB^9l@*=c@ge+^rDQL7O`0s~#kx*Jx?;*^pja5W-tlJV&owqYHD z_=q)Y8TcgaKin8tw6>YP(egX@CvDgua0D1jxpHdRWhl2C4w?f3gu^n)={-CJ4?44_ zj?x!*SABiJugPD%PQq+r1LuNC?kBUF;{gXlaAautF zy6wN2LkmjrM;80E-6d$kO&r!W*cb`v5F0yEuGN^hj2};JtPw*$&DS*}iRpiqw1F8* z{#Z=3s@72H*mjgUnQipoq}5@Vn@r!pmL16z~oAefCE-JB@2jj?&6 zxNu`8d3Z5$^rxAEJ3IN}_GU+TMnrbbyr=Zk@8rG|+FEklLnXY8m5doiklBHLmxe4R zwXOgD0EWtVu`RT`RXOhb;fVvzb)Hj2LBw{%D@_z~Fu}GShA*!<&nW+CG2crUY~p*~ zZbtKjL+-LAZ(6=Lj!{8QJ27%dcxvv2*5v&ju#Svg%1h6GEDFH`esn}f{W-_4m|-0W zxG5$Sxg9o3o3Tn$C(twA^{}FC#HSPM-eTmoN2a0ExfeXC2FZ52zV!r$9YV*48x(Az zR(s?{b@5~9wt3{JKk!C8`|g^>Yq2aUWleoiJSe^;L^1XAHsee~whoOC9soU@V@h{n z?3sa>gnP(BE?m>49h>re^&am^OynA!&~k7L)UF7K)e)SW;3g>w>{A@F@3!V; z%I-Mo38vZ>?OI6IIa&oeTyRyJnGo#ts&Ms$hkko>qs=~LAF1eGOKZ#Yobc(VATH|8 z-_EZBU#+ThxiHst-19&`*7o$Eqc5j6Rg1xYn@?`auGOgn)OF>uN66TlTsp{GIO4M zGAIN&5R1$-I-X}$7X_bc!lNpVD=mE<8QhheNEQv8miTNej+U+CW#$+&Xzq|lEm0jl ztwFg^D);Y8M#vh%M9?7ya&rP2#hKi(u3=B%WA70iZxShz)${~{E_6&r9CWZ|3N@uY zBz4a;d5%Y;YVoJJsvRq1-zB^(1Dys~d_DDod44Z<@^;wWv}Y|tJn@gTFD3u=UKi2AKfY#tpY?bWQ1-`ICFuDBF1 z)f?&Hl1PqS7_p9^VqOKX3|bQcM@-A`k%Zcy6wX393X^Q2nT>kt) zaptmH>CuuIDpB9bVhwz7(l~qgaLy|PY@Yrj2-n^oB>_6S2iL~r0v25MUhugo?;50! zIQ2m45pVa$L5?j0gLB&xNVec-MCV(g=(qLo#K>i7RyX_eru3OVzlAQ^FxTH%1nF}= zidC8q{ra6Yv@44115MBPPG#tZP|z;RP$;j@&WL=YsgkDPQAg$v66r=U)2`pw2US=h zI#p}x;AxQ`MOKS3dySOEQnE)&bVy=as|hzkN4miGqVOp_|J9f50kz4mb@;qv$l;yvPvEemYb>>$qaBsoo+xk>-y_a~vZ2!5Bhaz2D9 zqumrg%Vy>{WoGAz-sfftMTQu$OuX-R?{T)3f1)9BG325rFa20clX66CFT{xTIWyOD z4XJ$KgviLbKVTV>MIvM*@mB3?RuGrf(JsdAJ2-6;BoUS>TrHK59ngu0XOLiWgDVw3 z;hx&-^(gY&FE=|@F6UKer2p$bhiWQq8V&an1rqQTo8gR8R_M%f%q%OHT znZ6h~Ys)@k2_r69y1KT_erUHROKzg}&9?o@dxOl8nH)Iiyi`f1mPk=hQ55sVWJjtf zsRS)vrYxbI>~h|*onRmlpM|xDOg5ytc7~mA*4GkLs(02)_8CR>K;N*K+yhBTT@J9E zp9^?Xo*-Jz1G86dJpyv0qI;6N1GC108xemCfMdR4L|btQQgc(cb| z(x>(0sGs7?{K%Q4n~*1h#-A@SvE1Kl>Mq2@K7FXg*U@~7tkJ7pVt~HwWbvBcl^Aoi zdls5}haf(5nwIxkuVVIrHLDZ2s8LcW7|)RDh}kgUE4K4$ry;<SIU3SbK>Q;0gLxSspgECYM_rE~BR+EEeh_Zj`?OFrg3jt-`w z+xaHalb7$XxXvW^M{8psbS7}W1X=SnRDv-~*cdGNW2V+}Q5l^iyM_N=F~Zjki0Iq;p=VW<1%8>jKMzzg5gsB$-an-i~Ky=c&+`3 z^(AA|gfY9dXV{xG^hoX8qe~T*u-PI0Xz_3craW{nGUQ|doT z_b4pw?iBTIH|xm%fiA6`TsYf;;D<(;k>8SGXh{cJ5s?^{XS$_k!lr?aBD!v{EP5 zeQ393a}XR=_3{zU)uyB9P);X;zutpqb+U;I>E^sh1hQZ3=!r3y0d)mQ(K75}iZqmG z>#w}ho9xa2plSM?DvKB1PxYl?gYQg)9G=LFpvM#Dczz7FamLm!Ir{WjeM44mu^8&&^>cTFDqn1~0^UO*y}EAXoKdPy&C2jxz|9{GQ0$%gjLCLJHULJtm|P0G3P2?Lq`$3Y%NO7 z1tBAITfI|>ol($v@xzt`IHNo>!2|XNNSIhTy{XyP)RRTX@1h+9?WPz)MgD8{HJjK4 z0#{-cz_BXd=J}__W&|dtk`Zl4kGWnqE2pn8o@&-T3QunnH>2?w(>Uwn^GKlq zYTiR(mdUg*+4NE+3%N@^81<7_;*Eprin;5{GnT1z$6A|KyP{m9@_0uBteS%D!W5(W zFQoaIio#!(FyUR?VEo?##pJzsJ^4pEDv7)`S)mRhyY`a_KCdommI*dTQ=e%npQ#zI z6}<4cP#YpI{K+rdPB!1-`6C%kNOL9Fzo($woh_1%h9Z4GCPka5HGf@s@9A_b0h(ZyvM2)sdb08&u0!W(sbk)D z`(Moq-2^+lUS%g;1kH1#VJTHJoy*djx(2@{#D@5vWb8K=%m*7+Y}1(=+6Dv5jCG=C zo_6A$s2FT6?Sl)p{pY(CAqzSNJ*_|4jr#r`5P%5pzp-KoUf=tcn#4SN4rP9SaHK>| zgD=gLls}AR9eS*Gn`im`jJvAOmt)<3Ja6bGPm!(rJfv*xdWATn6iQE&+7tbn%oDIS zA#{`zbnT?w3=iE8NlaTDiF&*?Z%Y)-ovh`C=)ME1u*U{N*U z$d=C2ujP!U%&@sjK7{k@Kq|}l@B%YompSVCj|sl+4P-A77m75V=ur-w9;RDkuDkyH z^6u>W*7?{v&y(q^{X5$=Q7xx}+o6-&tqI5iOpqv_xd1yE{kKUJn;;`s|0n+RnuDnB zE+>Qd2H8ElK9=nES;|FE{99*VfZL zJYk3TDV|N0e;$#l32@vN`z5g(vQN}GqL62M&lT&hxl+@@IMwN6b&IbgFD3f_{aw0R zo{HNFopT-VwJ|FNtRIvVEpc0~kK)j97=>y8+UyYo>V{7(U0c)Igo2)&FgK}4?(Gw) z>yYKvIUoD8DA!IKi1>^u9hz^|RK2evf2^lY)3Xh$x~oim=unguDP%3%S8~V)<&X64 zlU(;pLW-)s5&Z)xD<0`Gb;C)I-NXZXM-2(@L+<@p>1?Q~3@nR@{fR9NR1hJE63gFy z!d>9C>mvm9kxA|^6IsK89sW|uEw%s4pCWzj@z-0h9PfzpuPnRll=lBMcKrW>n*HxO zN=qcay_O2AuXj7%R69I8{7?Px3zAGhr9SfxLxqNh{x^#JJ59r1FmqOUIj5hb$-jB+!J(o2;^NKaU{rhiJ0Bk(^?$3E zRf-gG^clLkNn_+9dRP7}T}J$ymE}}PnG`&57S6yb%YOy+uJoeJueJJRB|0`|p`HJ! ugeD1e$m=Y1?;`#8xc)zHidJsF!=+J)=M*m^yS|5ge0;AWTP|(-?SB9U-zBmD literal 0 HcmV?d00001 diff --git a/docs/Images/Screens/OvertakesWindowExemple.png b/docs/Images/Screens/OvertakesWindowExemple.png new file mode 100644 index 0000000000000000000000000000000000000000..c733e24b5f76bdbc40114f6eba4be7d7fc114732 GIT binary patch literal 21899 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 ozx$Wq9k`5O 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 GetDriverLaptimes(string driverName,int numberOfLaptimes) +{ + int driverId = GetDriverID(driverName); + List<(int LapTime, int Lap)> lapData = new List<(int LapTime, int Lap)>(); + string selectQuery = "Select LapTime,Lap from Stats WHERE DriverID = @driverID ORDER BY Lap DESC LIMIT @limit"; + using (var command = new SQLiteCommand(selectQuery, Connection)) + { + command.Parameters.AddWithValue("@driverID", driverId); + command.Parameters.AddWithValue("@limit", numberOfLaptimes); + + SQLiteDataReader reader = command.ExecuteReader(); + while (reader.Read()) + { + int lapTime = reader.GetInt32(0); + int lap = reader.GetInt32(1); + lapData.Add((lapTime, lap)); + } + } + return lapData; +} + +public void Display(){ + List<(int LapTime, int Lap)> lapsInfos = Storage.GetDriverLaptimes(driverName, 5); + int id = 0; + foreach ((int LapTime, int Lap) lapData in lapsInfos){ + Labels[id].Text = LapTime; + id++; + } +} +``` + +Note: Le code montré ici n'est pas forcément le code utilisé dans le projet. + +D'une certaine facon les fenêtres de bataille et de dépassements sont aussi des hybrides. + +!["Exemple fenêtre des batailles"](./Images/Screens/BattlesWindowExemple.png) + +Ici ce sont les batailles qui sont représentées. Aucune donnée n'est calculée, c'est litterallement directement les données de la F1TV, mais la nuance est qu'on ne montre que les pilotes qui sont en train de se battre et que on leur assigne une couleur selon à quel point ils sont proches. On a une plus-value sur la F1TV sans pour autant faire de monstres calculs. + +Pour info, les pilotes considèrés comme êtant en train de se battre sont les pilotes à moins de trois secondes les uns des autres et les couleurs sont suivantes : + +- Vert : Dans la zone de DRS (Moins d'une seconde) +- Jaune : Plus d'une seconde +- Noir : Plus de deux secondes + +!["Exemple fenêtre des dépassements"](./Images/Screens/OvertakesWindowExemple.png) + +La c'est l'historique des dépassements qui est affiché. On pourrait presque dire que c'est un affichage complêtement calculé car ce ne sont pas des informations disponibles directement sur la F1TV cependant je dirais que cela reste un hybride car il n'y a aucuns calculs. On regarde juste les différences entre l'ancienne position d'un pilote et la nouvelle et on affiche les changements. + +##### Affichage totalement calculé + +L'affichage complêtement calculé est un type d'affichage qui ne montre aucune information trouvée sur la page de la F1TV. C'est le premier affichage à traiter l'information qu'il trouve et il retourne des informations nouvelles. La nuance avec les affichages prédictif est qu'il ne crée pas réellement de l'information, il la déduit. + +Le but est de prendre un certain nombre d'informations trouvées sur la page de la F1TV et de calculer des choses pour faire ressortir des tendances à l'utilisateur. Cependant on reste sur des informations factuelles. Ce sont des infos déduites que techniquement unn humain avec une bonne mémoire et fort en calcul mental pourrait faire. Mais la c'est fait automatiquement pour tous les pilotes et c'est affiché de sorte à faire ressortir les valeurs spéciales. + +Comme c'est un peu plus abstrait, je pense qu'un exemple vaut mieux que 1000 mots. + +!["Exemple de fenêtre d'informations totalement calculées"](./Images/Screens/SlowestAndFastestExemple.png) + +Ci dessus on peut voir un bon exemple. C'est une fenêtre qui montre qui sont les pilotes les plus rapides et les moins rapides et qui montre la différence de temps au tour. + +Cette information est totalement déduite et n'est en aucun cas trouvable sur la F1TV mais elle n'est pas inventée. Elle est simplement calculée. + +La formule est assez simple, je prend les cinq derniers temps au tour de tous les pilotes. Je fais une moyenne qui donne un temps. Et ensuite je trie les pilotes en fonction de ce temps et je n'affiche que les cinq plus rapide et les cinq plus lents. Ensuite il suffit de prendre le temps le plus rapide et faire une petite soustraction pour avoir l'écart. + +C'est une stat assez intéressante car elle lisse les différences d'un tour à l'autre et fait ressortir une tendance. On peut voir pour le grand prix de monaco 2023 par exemple, le moment ou les pneus pluie deviennent plus intéressants que les pneus secs car on voit que les pilotes les plus rapides sont les pilotes de fond de grille qui ont chaussé les pneus pluie en premier tandis que les plus lents sont les pilotes sur pneus secs vieux. + +Voici un bout de code qui s'occupe de faire les calculs : + +```Csharp +List<(int avg, string driverName)> averages = new List<(int avg, string driverName)>(); + foreach (DriverData driver in LiveDriverDataLogs[LiveDriverDataLogs.Count - 1]) + { + //We want to recover the last 5 lap times + List<(int lapTime,int lap)> laps = Storage.GetDriverLaptimes(driver.Name,5); + if(laps.Count > 0) + { + int avg = 0; + foreach ((int lapTime, int lap) lap in laps) + { + avg += lap.lapTime; + } + avg = avg / laps.Count; + averages.Add((avg, driver.Name)); + } + } +``` + +#### Affichage prédictif + +C'est ici que ca devient vraiment dommage, le projet a mannqué de temps pour implémenter des affichages prédictifs mais le potentiel est la ! + +Un affichage prédictif est un affichage qui crée des informations à partir des infos qu'il a mais qui fait plus qu'un calcul. Le but est de tenter de deviner quelque chose. + +Voici des exemples d'affichages prédictifs qui pourraient être mis en place averc l'architecture actuelle du projet : + +- Si un pilote a des pneus depuis plus de 20 tours et que son temps au tour est en chute libre depuis cinq tours. Alors ce pilote va peut-être devoir s'arrêter. +- Si un pilote tourne une seconde au tour plus vite que le pilote devant lui et que ce pilote est à 10 secondes devant, alors il devrait pouvoir le rattraper d'ici dix tours. +- Si un arrêt au stand est en moyenne de 23 secondes, alors un pilote 3ème ressortirais potentiellement 7ème si il s'arrête maintenant. + +Tous ces exemples sont des mini algorythmes prédictifs qui pourraient être implémentés assez facilement dans l'architecture actuelle du projet et pourraient apporter une immense plus-value si ils sont bien paramêtrés. + +On peut même imaginer que l'algorythme se corrige tout seul si il voit qu'il a eu tort pour que les course suivante il puisse mieux s'en sortir. + +Les possibilitées sont infinies ! ## Tests diff --git a/temp_annexes/Code/ConfigurationTool.md b/temp_annexes/Code/ConfigurationTool.md new file mode 100644 index 0000000..fc2b89a --- /dev/null +++ b/temp_annexes/Code/ConfigurationTool.md @@ -0,0 +1,198 @@ +# ConfigurationTool.cs + +``` cs +/// Author : Maxime Rohmer +/// Date : 08/05/2023 +/// File : ConfigurationTool.cs +/// Brief : Class that contains all the methods needed to create a config file for the OCR +/// Version : 0.1 + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tesseract; +using System.IO; + +namespace Test_Merge +{ + public class ConfigurationTool + { + public Zone MainZone; + public const int NUMBER_OF_DRIVERS = 20; + public const int NUMBER_OF_ZONES = 9; + public const string CONFIGS_FOLDER_NAME = "./Presets/"; + + public ConfigurationTool(Bitmap fullImage, Rectangle mainZoneDimensions) + { + MainZone = new Zone(fullImage, mainZoneDimensions,"Main"); + AutoCalibrate(); + } + public void ResetMainZone() + { + MainZone.ResetZones(); + } + public void ResetWindows() + { + MainZone.ResetWindows(); + } + public void SaveToJson(List drivers, string configName) + { + string JSON = ""; + + JSON += "{" + Environment.NewLine; + JSON += MainZone.ToJSON() + "," + Environment.NewLine; + JSON += "\"Drivers\":[" + Environment.NewLine; + + for (int i = 0; i < drivers.Count; i++) + { + JSON += "\"" + drivers[i] + "\""; + if (i < drivers.Count - 1) + JSON += ","; + JSON += Environment.NewLine; + } + + JSON += "]" + Environment.NewLine; + + JSON += "}"; + + if (!Directory.Exists(CONFIGS_FOLDER_NAME)) + Directory.CreateDirectory(CONFIGS_FOLDER_NAME); + + string path = CONFIGS_FOLDER_NAME + configName; + + if (File.Exists(path + ".json")) + { + //We need to create a new name + int count = 2; + while (File.Exists(path + "_" + count + ".json")) + { + count++; + } + path += "_" + count + ".json"; + } + else + { + path += ".json"; + } + + File.WriteAllText(path, JSON); + } + public void AddWindows(List rectangles) + { + foreach (Zone driverZone in MainZone.Zones) + { + Bitmap zoneImage = driverZone.ZoneImage; + + for (int i = 1; i <= rectangles.Count; i++) + { + switch (i) + { + case 1: + //First zone should be the driver's Position + driverZone.AddWindow(new DriverPositionWindow(driverZone.ZoneImage, rectangles[i - 1], false)); + break; + case 2: + //First zone should be the Gap to leader + driverZone.AddWindow(new DriverGapToLeaderWindow(driverZone.ZoneImage, rectangles[i - 1], false)); + break; + case 3: + //First zone should be the driver's Lap Time + driverZone.AddWindow(new DriverLapTimeWindow(driverZone.ZoneImage, rectangles[i - 1], false)); + break; + case 4: + //First zone should be the driver's DRS status + driverZone.AddWindow(new DriverDrsWindow(driverZone.ZoneImage, rectangles[i - 1], false)); + break; + case 5: + //First zone should be the driver's Tyre's informations + driverZone.AddWindow(new DriverTyresWindow(driverZone.ZoneImage, rectangles[i - 1], false)); + break; + case 6: + //First zone should be the driver's Name + driverZone.AddWindow(new DriverNameWindow(driverZone.ZoneImage, rectangles[i - 1], false)); + break; + case 7: + //First zone should be the driver's First Sector + driverZone.AddWindow(new DriverSectorWindow(driverZone.ZoneImage, rectangles[i - 1], 1, false)); + break; + case 8: + //First zone should be the driver's Second Sector + driverZone.AddWindow(new DriverSectorWindow(driverZone.ZoneImage, rectangles[i - 1], 2, false)); + break; + case 9: + //First zone should be the driver's Position Sector + driverZone.AddWindow(new DriverSectorWindow(driverZone.ZoneImage, rectangles[i - 1], 3, false)); + break; + } + } + } + } + public void AutoCalibrate() + { + List detectedText = new List(); + List zones = new List(); + + TesseractEngine engine = new TesseractEngine(Window.TESS_DATA_FOLDER.FullName, "eng", EngineMode.Default); + Image image = MainZone.ZoneImage; + var tessImage = Pix.LoadFromMemory(Window.ImageToByte(image)); + + Page page = engine.Process(tessImage); + using (var iter = page.GetIterator()) + { + iter.Begin(); + do + { + Rect boundingBox; + if (iter.TryGetBoundingBox(PageIteratorLevel.Word, out boundingBox)) + { + //var text = iter.GetText(PageIteratorLevel.Word).ToUpper(); + //We remove all the rectangles that are definitely too big + if (boundingBox.Height < image.Height / NUMBER_OF_DRIVERS) + { + //Now we add a filter to only get the boxes in the right because they are much more reliable in size + if (boundingBox.X1 > image.Width / 2) + { + //Now we check if an other square box has been found roughly in the same y axis + bool match = false; + //The tolerance is roughly half the size that a window will be + int tolerance = (image.Height / NUMBER_OF_DRIVERS) / 2; + + foreach (Rectangle rect in detectedText) + { + if (rect.Y > boundingBox.Y1 - tolerance && rect.Y < boundingBox.Y1 + tolerance) + { + //There already is a rectangle in this line + match = true; + } + } + //if nothing matched we can add it + if (!match) + detectedText.Add(new Rectangle(boundingBox.X1, boundingBox.Y1, boundingBox.Width, boundingBox.Height)); + } + } + } + } while (iter.Next(PageIteratorLevel.Word)); + } + //DEBUG + int i = 1; + foreach (Rectangle Rectangle in detectedText) + { + Rectangle windowRectangle; + Size windowSize = new Size(image.Width, image.Height / NUMBER_OF_DRIVERS); + Point windowLocation = new Point(0, (Rectangle.Y + Rectangle.Height / 2) - windowSize.Height / 2); + windowRectangle = new Rectangle(windowLocation, windowSize); + //We add the driver zones + Zone driverZone = new Zone(MainZone.ZoneImage, windowRectangle, "DriverZone"); + MainZone.AddZone(driverZone); + + driverZone.ZoneImage.Save("Driver" + i+".png"); + i++; + } + } + } +} + +``` diff --git a/temp_annexes/Code/DriverData.md b/temp_annexes/Code/DriverData.md new file mode 100644 index 0000000..c75f38e --- /dev/null +++ b/temp_annexes/Code/DriverData.md @@ -0,0 +1,107 @@ +# DriverData.cs + +``` cs +/// Author : Maxime Rohmer +/// Date : 08/05/2023 +/// File : DriverData.cs +/// Brief : Class used to store Driver informations +/// Version : 0.1 + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Test_Merge +{ + public class DriverData + { + public bool DRS; //True = Drs is opened + public int GapToLeader; //In ms + public int LapTime; //In ms + public string Name; //Ex: LECLERC + public int Position; //Ex: 1 + public int Sector1; //in ms + public int Sector2; //in ms + public int Sector3; //in ms + public Tyre CurrentTyre;//Ex Soft 11 laps + + public DriverData(bool dRS, int gapToLeader, int lapTime, string name, int position, int sector1, int sector2, int sector3, Tyre tyre) + { + DRS = dRS; + GapToLeader = gapToLeader; + LapTime = lapTime; + Name = name; + Position = position; + Sector1 = sector1; + Sector2 = sector2; + Sector3 = sector3; + CurrentTyre = tyre; + } + public DriverData() + { + DRS = false; + GapToLeader = -1; + LapTime = -1; + Name = "Unknown"; + Position = -1; + Sector1 = -1; + Sector2 = -1; + Sector3 = -1; + CurrentTyre = new Tyre(Tyre.Type.Undefined, -1); + } + /// + /// Method that displays all the data found in a string + /// + /// string containing all the driver datas + public override string ToString() + { + string result = ""; + + //Position + result += "Position : " + Position + Environment.NewLine; + //Gap + result += "GapToLeader : " + Reader.ConvertMsToTime(GapToLeader) + Environment.NewLine; + //LapTime + result += "LapTime : " + Reader.ConvertMsToTime(LapTime) + Environment.NewLine; + //DRS + result += "DRS : " + DRS + Environment.NewLine; + //Tyres + result += "Uses " + CurrentTyre.Coumpound + " tyre " + CurrentTyre.NumberOfLaps + " laps old" + Environment.NewLine; + //Name + result += "DriverName : " + Name + Environment.NewLine; + //Sector 1 + result += "Sector1 : " + Reader.ConvertMsToTime(Sector1) + Environment.NewLine; + //Sector 1 + result += "Sector2 : " + Reader.ConvertMsToTime(Sector2) + Environment.NewLine; + //Sector 1 + result += "Sector3 : " + Reader.ConvertMsToTime(Sector3) + Environment.NewLine; + + return result; + } + } + //Structure to store tyres infos + public struct Tyre + { + //If new tyres were to be added you will have to need to change this enum + public enum Type + { + Soft, + Medium, + Hard, + Inter, + Wet, + Undefined + } + public Type Coumpound; + public int NumberOfLaps; + public Tyre(Type type, int laps) + { + Coumpound = type; + NumberOfLaps = laps; + } + } +} + +``` diff --git a/temp_annexes/Code/DriverDrsWindow.md b/temp_annexes/Code/DriverDrsWindow.md new file mode 100644 index 0000000..d5aa591 --- /dev/null +++ b/temp_annexes/Code/DriverDrsWindow.md @@ -0,0 +1,103 @@ +# DriverDrsWindow.cs + +``` cs +/// Author : Maxime Rohmer +/// Date : 08/05/2023 +/// File : DriverDrsWindow.cs +/// Brief : Window containing DRS related method and infos +/// Version : 0.1 + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tesseract; + +namespace Test_Merge +{ + internal class DriverDrsWindow:Window + { + private static int EmptyDrsGreenValue = -1; + private static Random rnd = new Random(); + public DriverDrsWindow(Bitmap image, Rectangle bounds,bool generateEngine = true) : base(image, bounds,generateEngine) + { + Name = "DRS"; + } + public override async Task DecodePng() + { + bool result = false; + int greenValue = GetGreenPixels(); + if (EmptyDrsGreenValue == -1) + EmptyDrsGreenValue = greenValue; + + if (greenValue > EmptyDrsGreenValue + EmptyDrsGreenValue / 100 * 30) + result = true; + + return result; + } + private unsafe int GetGreenPixels() + { + int tot = 0; + + Bitmap bmp = WindowImage; + Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height); + BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadOnly, bmp.PixelFormat); + int bytesPerPixel = Bitmap.GetPixelFormatSize(bmp.PixelFormat) / 8; + + unsafe + { + byte* ptr = (byte*)bmpData.Scan0.ToPointer(); + for (int y = 0; y < bmp.Height; y++) + { + byte* currentLine = ptr + (y * bmpData.Stride); + for (int x = 0; x < bmp.Width; x++) + { + byte* pixel = currentLine + (x * bytesPerPixel); + + byte blue = pixel[0]; + byte green = pixel[1]; + byte red = pixel[2]; + + if (green > blue * 1.5 && green > red * 1.5) + { + tot++; + } + } + } + } + bmp.UnlockBits(bmpData); + + return tot; + } + public Rectangle GetBox() + { + var tessImage = Pix.LoadFromMemory(ImageToByte(WindowImage)); + Engine.SetVariable("tessedit_char_whitelist", ""); + Page page = Engine.Process(tessImage); + + using (var iter = page.GetIterator()) + { + iter.Begin(); + do + { + Rect boundingBox; + + // Get the bounding box for the current element + if (iter.TryGetBoundingBox(PageIteratorLevel.Word, out boundingBox)) + { + page.Dispose(); + return new Rectangle(boundingBox.X1, boundingBox.X2, boundingBox.Width, boundingBox.Height); + } + } while (iter.Next(PageIteratorLevel.Word)); + + page.Dispose(); + return new Rectangle(0, 0, 0, 0); + } + } + } +} + +``` diff --git a/temp_annexes/Code/DriverGapToLeaderWindow.md b/temp_annexes/Code/DriverGapToLeaderWindow.md new file mode 100644 index 0000000..dea25f4 --- /dev/null +++ b/temp_annexes/Code/DriverGapToLeaderWindow.md @@ -0,0 +1,37 @@ +# DriverGapToLeaderWindow.cs + +``` cs +/// Author : Maxime Rohmer +/// Date : 08/05/2023 +/// File : DriverGapToLeaderWindow.cs +/// Brief : Window containing infos about the gap to the leader of a driver +/// Version : 0.1 + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Test_Merge +{ + internal class DriverGapToLeaderWindow:Window + { + public DriverGapToLeaderWindow(Bitmap image, Rectangle bounds, bool generateEngine = true) : base(image, bounds,generateEngine) + { + Name = "GapToLeader"; + } + /// + /// Decodes the gap to leader using Tesseract OCR + /// + /// + public override async Task DecodePng() + { + int result = await GetTimeFromPng(WindowImage, OcrImage.WindowType.Gap, Engine); + return result; + } + } +} + +``` diff --git a/temp_annexes/Code/DriverLapTimeWindow.md b/temp_annexes/Code/DriverLapTimeWindow.md new file mode 100644 index 0000000..4281842 --- /dev/null +++ b/temp_annexes/Code/DriverLapTimeWindow.md @@ -0,0 +1,37 @@ +# DriverLapTimeWindow.cs + +``` cs +/// Author : Maxime Rohmer +/// Date : 08/05/2023 +/// File : DriverLapTimeWindow +/// Brief : Window containing infos about the lap time of a driver +/// Version : 0.1 + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Drawing; + +namespace Test_Merge +{ + internal class DriverLapTimeWindow:Window + { + public DriverLapTimeWindow(Bitmap image, Rectangle bounds, bool generateEngine = true) : base(image, bounds,generateEngine) + { + Name = "LapTime"; + } + /// + /// Decodes the lap time contained in the image using OCR Tesseract + /// + /// The laptime in int (ms) + public override async Task DecodePng() + { + int result = await GetTimeFromPng(WindowImage, OcrImage.WindowType.LapTime, Engine); + return result; + } + } +} + +``` diff --git a/temp_annexes/Code/DriverNameWindow.md b/temp_annexes/Code/DriverNameWindow.md new file mode 100644 index 0000000..153a87b --- /dev/null +++ b/temp_annexes/Code/DriverNameWindow.md @@ -0,0 +1,63 @@ +# DriverNameWindow.cs + +``` cs +/// Author : Maxime Rohmer +/// Date : 08/05/2023 +/// File : DriverNameWindow +/// Brief : Window containing infos about the name of the driver +/// Version : 0.1 + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Drawing; + +namespace Test_Merge +{ + public class DriverNameWindow : Window + { + public static Random rnd = new Random(); + public DriverNameWindow(Bitmap image, Rectangle bounds, bool generateEngine = true) : base(image, bounds,generateEngine) + { + Name = "Name"; + } + /// + /// Decodes using OCR wich driver name is in the image + /// + /// + /// The driver name in string + public override async Task DecodePng(List DriverList) + { + string result = ""; + result = await GetStringFromPng(WindowImage, Engine); + + if (!IsADriver(DriverList, result)) + { + //I put everything in uppercase to try to lower the chances of bad answers + result = FindClosestMatch(DriverList.ConvertAll(d => d.ToUpper()), result.ToUpper()); + } + return result; + } + /// + /// Verifies that the name found in the OCR is a valid name + /// + /// + /// + /// If ye or no the driver exists + private static bool IsADriver(List driverList, string potentialDriver) + { + bool result = false; + //I cant use drivers.Contains because it has missmatched cases and all + foreach (string name in driverList) + { + if (name.ToUpper() == potentialDriver.ToUpper()) + result = true; + } + return result; + } + } +} + +``` diff --git a/temp_annexes/Code/DriverPositionWindow.md b/temp_annexes/Code/DriverPositionWindow.md new file mode 100644 index 0000000..74ce045 --- /dev/null +++ b/temp_annexes/Code/DriverPositionWindow.md @@ -0,0 +1,47 @@ +# DriverPositionWindow.cs + +``` cs +/// Author : Maxime Rohmer +/// Date : 08/05/2023 +/// File : DriverPosition.cs +/// Brief : Window containing infos about the position of a driver. +/// Version : 0.1 + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Drawing; + +namespace Test_Merge +{ + public class DriverPositionWindow:Window + { + public DriverPositionWindow(Bitmap image, Rectangle bounds, bool generateEngine = true) : base(image, bounds,generateEngine) + { + Name = "Position"; + } + /// + /// Decodes the position number using Tesseract OCR + /// + /// The position of the pilot in int + public override async Task DecodePng() + { + string ocrResult = await GetStringFromPng(WindowImage, Engine, "0123456789"); + + int position; + try + { + position = Convert.ToInt32(ocrResult); + } + catch + { + position = -1; + } + return position; + } + } +} + +``` diff --git a/temp_annexes/Code/DriverSectorWindow.md b/temp_annexes/Code/DriverSectorWindow.md new file mode 100644 index 0000000..dc342d7 --- /dev/null +++ b/temp_annexes/Code/DriverSectorWindow.md @@ -0,0 +1,37 @@ +# DriverSectorWindow.cs + +``` cs +/// Author : Maxime Rohmer +/// Date : 08/05/2023 +/// File : DriverSectorWindow.cs +/// Brief : Window containing infos about a driver sector time. Can be the first second or third, does not matter. +/// Version : 0.1 + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Drawing; + +namespace Test_Merge +{ + internal class DriverSectorWindow:Window + { + public DriverSectorWindow(Bitmap image, Rectangle bounds, int sectorId, bool generateEngine = true) : base(image, bounds,generateEngine) + { + Name = "Sector"+sectorId; + } + /// + /// Decodes the sector + /// + /// the sector time in int (ms) + public override async Task DecodePng() + { + int ocrResult = await GetTimeFromPng(WindowImage, OcrImage.WindowType.Sector, Engine); + return ocrResult; + } + } +} + +``` diff --git a/temp_annexes/Code/DriverTyresWindow.md b/temp_annexes/Code/DriverTyresWindow.md new file mode 100644 index 0000000..485c438 --- /dev/null +++ b/temp_annexes/Code/DriverTyresWindow.md @@ -0,0 +1,146 @@ +# DriverTyresWindow.cs + +``` cs +/// Author : Maxime Rohmer +/// Date : 08/05/2023 +/// File : DriverTyresWindow.cs +/// Brief : Window containing infos about a driver's tyre +/// Version : 0.1 + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Drawing; + +namespace Test_Merge +{ + public class DriverTyresWindow:Window + { + private static Random rnd = new Random(); + int seed = rnd.Next(0, 10000); + + //Those are the colors I found but you can change them if they change in the future like in 2019 + public static Color SOFT_TYRE_COLOR = Color.FromArgb(0xff, 0x00, 0x00); + public static Color MEDIUM_TYRE_COLOR = Color.FromArgb(0xf5, 0xbf, 0x00); + public static Color HARD_TYRE_COLOR = Color.FromArgb(0xa4, 0xa5, 0xa8); + public static Color INTER_TYRE_COLOR = Color.FromArgb(0x00, 0xa4, 0x2e); + public static Color WET_TYRE_COLOR = Color.FromArgb(0x27, 0x60, 0xa6); + public static Color EMPTY_COLOR = Color.FromArgb(0x20, 0x20, 0x20); + + public DriverTyresWindow(Bitmap image, Rectangle bounds, bool generateEngine = true) : base(image, bounds,generateEngine) + { + Name = "Tyres"; + } + /// + /// This will decode the content of the image + /// + /// And object containing what was on the image + public override async Task DecodePng() + { + return await GetTyreInfos(); + } + /// + /// Method that will decode whats on the image and return the tyre infos it could manage to recover + /// + /// A tyre object containing tyre infos + private async Task GetTyreInfos() + { + Bitmap tyreZone = GetSmallBitmapFromBigOne(WindowImage, FindTyreZone()); + Tyre.Type type = Tyre.Type.Undefined; + type = GetTyreTypeFromColor(OcrImage.GetAvgColorFromBitmap(tyreZone)); + int laps = -1; + + string number = await GetStringFromPng(tyreZone, Engine, "0123456789", OcrImage.WindowType.Tyre); + try + { + laps = Convert.ToInt32(number); + } + catch + { + //We could not convert the number so its a letter so its 0 laps old + laps = 0; + } + //tyreZone.Save(Reader.DEBUG_DUMP_FOLDER + "Tyre" + type + "Laps" + laps + '#' + rnd.Next(0, 1000) + ".png"); + return new Tyre(type, laps); + } + /// + /// Finds where the important part of the image is + /// + /// A rectangle containing position and dimensions of the important part of the image + private Rectangle FindTyreZone() + { + Bitmap bmp = WindowImage; + int currentPosition = bmp.Width; + int height = bmp.Height / 2; + Color limitColor = Color.FromArgb(0x50, 0x50, 0x50); + Color currentColor = Color.FromArgb(0, 0, 0); + + Size newWindowSize = new Size(bmp.Height - Convert.ToInt32((float)bmp.Height / 100f * 25f), bmp.Height - Convert.ToInt32((float)bmp.Height / 100f * 35f)); + + while (currentColor.R <= limitColor.R && currentColor.G <= limitColor.G && currentColor.B <= limitColor.B && currentPosition > 0) + { + currentPosition--; + currentColor = bmp.GetPixel(currentPosition, height); + } + + //Its here to let the new window include a little bit of the right + int CorrectedX = currentPosition - (newWindowSize.Width) + Convert.ToInt32((float)newWindowSize.Width / 100f * 10f); + int CorrectedY = Convert.ToInt32((float)newWindowSize.Height / 100f * 35f); + if (CorrectedX <= 0) + return new Rectangle(0, 0, newWindowSize.Width, newWindowSize.Height); + + return new Rectangle(CorrectedX, CorrectedY, newWindowSize.Width, newWindowSize.Height); + } + //This method has been created with the help of chatGPT + /// + /// Methods that compares a list of colors to see wich is the closest from the input color and decide wich tyre type it is + /// + /// The color that you found + /// The tyre type + public Tyre.Type GetTyreTypeFromColor(Color inputColor) + { + Tyre.Type type = Tyre.Type.Undefined; + List colors = new List(); + //dont forget that if for some reason someday F1 adds a new Tyre type you will need to add it in the constants but also here in the list + //You will also need to add it below in the Tyre object's enum and add an if in the end of this method + colors.Add(SOFT_TYRE_COLOR); + colors.Add(MEDIUM_TYRE_COLOR); + colors.Add(HARD_TYRE_COLOR); + colors.Add(INTER_TYRE_COLOR); + colors.Add(WET_TYRE_COLOR); + colors.Add(EMPTY_COLOR); + + Color closestColor = colors[0]; + int closestDistance = int.MaxValue; + foreach (Color color in colors) + { + int distance = Math.Abs(color.R - inputColor.R) + Math.Abs(color.G - inputColor.G) + Math.Abs(color.B - inputColor.B); + if (distance < closestDistance) + { + closestColor = color; + closestDistance = distance; + } + } + + //We cant use a switch as the colors cant be constants ... + if (closestColor == SOFT_TYRE_COLOR) + type = Tyre.Type.Soft; + if (closestColor == MEDIUM_TYRE_COLOR) + type = Tyre.Type.Medium; + if (closestColor == HARD_TYRE_COLOR) + type = Tyre.Type.Hard; + if (closestColor == INTER_TYRE_COLOR) + type = Tyre.Type.Inter; + if (closestColor == WET_TYRE_COLOR) + type = Tyre.Type.Wet; + if (closestColor == EMPTY_COLOR) + return Tyre.Type.Undefined; + + return type; + } + } +} + +``` diff --git a/temp_annexes/Code/F1TVEmulator.md b/temp_annexes/Code/F1TVEmulator.md new file mode 100644 index 0000000..d21be84 --- /dev/null +++ b/temp_annexes/Code/F1TVEmulator.md @@ -0,0 +1,293 @@ +# F1TVEmulator.cs + +``` cs +/// Author : Maxime Rohmer +/// Date : 08/05/2023 +/// File : F1TVEmulator.cs +/// Brief : Class that contains methods to emulate a browser and navigate the F1TV website +/// Version : 0.1 + +using OpenQA.Selenium; +using OpenQA.Selenium.Firefox; +using OpenQA.Selenium.Interactions; +using OpenQA.Selenium.Support.UI; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Test_Merge +{ + internal class F1TVEmulator + { + public const string COOKIE_HOST = ".formula1.com"; + public const string PYTHON_COOKIE_RETRIEVAL_FILENAME = "recoverCookiesCSV.py"; + public const string GECKODRIVER_FILENAME = @"geckodriver-v0.27.0-win64\geckodriver.exe"; + //BE CAREFULL IF YOU CHANGE IT HERE YOU NEED TO CHANGE IT IN THE PYTHON SCRIPT TOO + public const string COOKIES_CSV_FILENAME = "cookies.csv"; + + private FirefoxDriver Driver; + + private bool _ready; + private string _grandPrixUrl; + public string GrandPrixUrl { get => _grandPrixUrl; private set => _grandPrixUrl = value; } + public bool Ready { get => _ready; set => _ready = value; } + public F1TVEmulator(string grandPrixUrl) + { + GrandPrixUrl = grandPrixUrl; + Ready = false; + } + private void StartCookieRecovering() + { + string scriptPath = PYTHON_COOKIE_RETRIEVAL_FILENAME; + Process process = new Process(); + process.StartInfo.FileName = "python.exe"; + process.StartInfo.Arguments = scriptPath; + process.StartInfo.UseShellExecute = false; + process.StartInfo.RedirectStandardOutput = true; + process.Start(); + string output = process.StandardOutput.ReadToEnd(); + process.WaitForExit(); + } + public string GetCookie(string host, string name) + { + StartCookieRecovering(); + string value = ""; + List cookies = new List(); + using (var reader = new StreamReader(COOKIES_CSV_FILENAME)) + { + // Read the header row and validate column order + string header = reader.ReadLine(); + string[] expectedColumns = { "host_key", "name", "value", "path", "expires_utc", "is_secure", "is_httponly" }; + string[] actualColumns = header.Split(','); + for (int i = 0; i < expectedColumns.Length; i++) + { + if (expectedColumns[i] != actualColumns[i]) + { + throw new InvalidOperationException($"Expected column '{expectedColumns[i]}' at index {i} but found '{actualColumns[i]}'"); + } + } + + // Read each data row and parse values into a Cookie object + while (!reader.EndOfStream) + { + string line = reader.ReadLine(); + string[] fields = line.Split(','); + + string hostname = fields[0]; + string cookieName = fields[1]; + + if (hostname == host && cookieName == name) + { + value = fields[2]; + } + } + } + + return value; + } + public async Task Start() + { + Ready = false; + + string loginCookieName = "login"; + string loginSessionCookieName = "login-session"; + string loginCookieValue = GetCookie(COOKIE_HOST, loginCookieName); + string loginSessionValue = GetCookie(COOKIE_HOST, loginSessionCookieName); + + int windowWidth = 1920; + int windowHeight = 768; + + var service = FirefoxDriverService.CreateDefaultService(GECKODRIVER_FILENAME); + service.Host = "127.0.0.1"; + service.Port = 5555; + + FirefoxProfile profile = new FirefoxProfile(); + FirefoxOptions options = new FirefoxOptions(); + //profile.SetPreference("full-screen-api.ignore-widgets", true); + //profile.SetPreference("media.hardware-video-decoding.enabled", true); + //profile.SetPreference("full-screen-api.enabled", true); + options.Profile = profile; + profile.SetPreference("layout.css.devPixelsPerPx", "1.0"); + + options.AcceptInsecureCertificates = true; + options.AddArgument("--headless"); + //options.AddArgument("--start-maximized"); + //options.AddArgument("--window-size=1920x1080"); + //options.AddArgument("--width=" + windowWidth); + //options.AddArgument("--height=" + windowHeight); + //options.AddArgument("-window-size=1920x1080"); + //options.AddArgument("--width=1920"); + //options.AddArgument("--height=1080"); + //profile + + try + { + Driver = new FirefoxDriver(service, options); + } + catch + { + Ready = false; + return 101; + } + + Actions actions = new Actions(Driver); + var loginCookie = new Cookie(loginCookieName, loginCookieValue, COOKIE_HOST, "/", DateTime.Now.AddDays(5)); + var loginSessionCookie = new Cookie(loginSessionCookieName, loginSessionValue, COOKIE_HOST, "/", DateTime.Now.AddDays(5)); + + Driver.Navigate().GoToUrl("https://f1tv.formula1.com/"); + + Driver.Manage().Cookies.AddCookie(loginCookie); + Driver.Manage().Cookies.AddCookie(loginSessionCookie); + + try + { + Driver.Navigate().GoToUrl(GrandPrixUrl); + } + catch + { + //The url is not a valid url + Driver.Dispose(); + return 103; + } + + //Waits for the page to fully load + Driver.Manage().Timeouts().PageLoad = TimeSpan.FromSeconds(30); + + //Removes the cookie prompt + try + { + IWebElement conscentButton = Driver.FindElement(By.Id("truste-consent-button")); + conscentButton.Click(); + } + catch + { + //Could not locate the cookie button + Screenshot("ERROR104"); + Driver.Dispose(); + return 104; + } + + //Again waits for the page to fully load (when you accept cookies it takes a little time for the page to load) + //Cannot use The timeout because the feed loading is not really loading so there is not event or anything + Thread.Sleep(5000); + + //Switches to the Data channel + try + { + IWebElement dataChannelButton = Driver.FindElement(By.ClassName("data-button")); + dataChannelButton.Click(); + } + catch + { + //If the data button does not exists its because the user is not connected + Screenshot("ERROR102"); + Driver.Dispose(); + return 102; + } + + //Open settings + // Press the space key, this should make the setting button visible + // It does not matter if the feed is paused because when changing channel it autoplays + actions.SendKeys(OpenQA.Selenium.Keys.Space).Perform(); + //Clicks on the settings Icon + + int tries = 0; + bool success = false; + while (tries < 100 && !success) + { + Thread.Sleep(100); + try + { + IWebElement settingsButton = Driver.FindElement(By.ClassName("bmpui-ui-settingstogglebutton")); + settingsButton.Click(); + IWebElement selectElement = Driver.FindElement(By.ClassName("bmpui-ui-videoqualityselectbox")); + SelectElement select = new SelectElement(selectElement); + IWebElement selectOption = selectElement.FindElement(By.CssSelector("option[value^='1080_']")); + selectOption.Click(); + success = true; + } + catch + { + //Sometimes it can crash because it could not get the options to show up in time. When it happens just retry + success = false; + tries++; + } + } + + if (!success) + { + Screenshot("ERROR105"); + Driver.Dispose(); + return 105; + } + + Screenshot("BEFOREFULLSCREEN"); + + //Makes the feed fullscreen + //Driver.Manage().Window.Size = new System.Drawing.Size(windowWidth, windowHeight); + Driver.Manage().Window.Maximize(); + WebDriverWait wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(10)); + try + { + IWebElement fullScreenButton = Driver.FindElement(By.ClassName("bmpui-ui-fullscreentogglebutton")); + fullScreenButton.Click(); + } + catch + { + Screenshot("ERROR106"); + Driver.Dispose(); + return 106; + } + + Screenshot("AFTERFULLSCREEN"); + + //STARTUP FINISHED READY TO SCREENSHOT + Ready = true; + return 0; + } + public Bitmap Screenshot(string name = "TEST") + { + Bitmap result = new Bitmap(4242, 6969); + try + { + //Screenshot scrsht = ((ITakesScreenshot)Driver).GetScreenshot(); + //profileriver.SetPreference("layout.css.devPixelsPerPx", "1.0"); + + //Screenshot scrsht = Driver.GetFullPageScreenshot(); + Screenshot scrsht = Driver.GetScreenshot(); + + + byte[] screenshotBytes = Convert.FromBase64String(scrsht.AsBase64EncodedString); + MemoryStream stream = new MemoryStream(screenshotBytes); + + result = new Bitmap(stream); + //result.Save(name + ".png"); + scrsht.SaveAsFile(name + ".png"); + } + catch + { + //Nothing for now + } + return result; + } + public void Stop() + { + Ready = false; + Driver.Dispose(); + } + public void ResetDriver() + { + Ready = false; + Driver.Dispose(); + Driver = null; + } + } +} + +``` diff --git a/temp_annexes/Code/Form1.md b/temp_annexes/Code/Form1.md new file mode 100644 index 0000000..92eb895 --- /dev/null +++ b/temp_annexes/Code/Form1.md @@ -0,0 +1,32 @@ +# Form1.cs + +``` cs +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Test_Merge +{ + public partial class Form1 : Form + { + public Form1() + { + InitializeComponent(); + } + + private void btnSettings_Click(object sender, EventArgs e) + { + Settings settingsForm = new Settings(); + settingsForm.ShowDialog(); + MessageBox.Show(settingsForm.GrandPrixUrl + Environment.NewLine + settingsForm.GrandPrixName + Environment.NewLine + settingsForm.GrandPrixYear); + } + } +} + +``` diff --git a/temp_annexes/Code/OcrImage.md b/temp_annexes/Code/OcrImage.md new file mode 100644 index 0000000..f3bb55b --- /dev/null +++ b/temp_annexes/Code/OcrImage.md @@ -0,0 +1,544 @@ +# OcrImage.cs + +``` cs +/// Author : Maxime Rohmer +/// Date : 08/05/2023 +/// File : OcrImage.cs +/// Brief : Class containing all the methods used to enhance images for OCR +/// Version : 0.1 + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; + +namespace Test_Merge +{ + public class OcrImage + { + //this is a hardcoded value based on the colors of the F1TV data channel background you can change it if sometime in the future the color changes + //Any color that has any of its R,G or B channel higher than the treshold will be considered as being usefull information + public static Color F1TV_BACKGROUND_TRESHOLD = Color.FromArgb(0x50, 0x50, 0x50); + Bitmap InputBitmap; + public enum WindowType + { + LapTime, + Text, + Sector, + Gap, + Tyre, + } + + /// + /// Create a new Ocr image to help enhance the given bitmap for OCR + /// + /// The image you want to enhance + public OcrImage(Bitmap inputBitmap) + { + InputBitmap = inputBitmap; + } + /// + /// Enhances the image depending on wich type of window the image comes from + /// + /// The type of the window. Depending on it different enhancing features will be applied + /// The enhanced Bitmap + public Bitmap Enhance(WindowType type = WindowType.Text) + { + Bitmap outputBitmap = (Bitmap)InputBitmap.Clone(); + switch (type) + { + case WindowType.LapTime: + outputBitmap = Tresholding(outputBitmap, 185); + outputBitmap = Resize(outputBitmap, 2); + outputBitmap = Dilatation(outputBitmap, 1); + outputBitmap = Erode(outputBitmap, 1); + break; + case WindowType.Text: + outputBitmap = InvertColors(outputBitmap); + outputBitmap = Tresholding(outputBitmap, 165); + outputBitmap = Resize(outputBitmap, 2); + outputBitmap = Dilatation(outputBitmap, 1); + break; + case WindowType.Tyre: + outputBitmap = RemoveUseless(outputBitmap); + outputBitmap = Resize(outputBitmap, 4); + outputBitmap = Dilatation(outputBitmap, 1); + break; + default: + outputBitmap = Tresholding(outputBitmap, 165); + outputBitmap = Resize(outputBitmap, 4); + outputBitmap = Erode(outputBitmap, 1); + break; + } + return outputBitmap; + } + /// + /// Method that convert a colored RGB bitmap into a GrayScale image + /// + /// The Bitmap you want to convert + /// The bitmap in grayscale + public static Bitmap Grayscale(Bitmap inputBitmap) + { + Rectangle rect = new Rectangle(0, 0, inputBitmap.Width, inputBitmap.Height); + BitmapData bmpData = inputBitmap.LockBits(rect, ImageLockMode.ReadWrite, inputBitmap.PixelFormat); + int bytesPerPixel = Bitmap.GetPixelFormatSize(inputBitmap.PixelFormat) / 8; + + unsafe + { + byte* ptr = (byte*)bmpData.Scan0.ToPointer(); + for (int y = 0; y < inputBitmap.Height; y++) + { + byte* currentLine = ptr + (y * bmpData.Stride); + for (int x = 0; x < inputBitmap.Width; x++) + { + byte* pixel = currentLine + (x * bytesPerPixel); + + byte blue = pixel[0]; + byte green = pixel[1]; + byte red = pixel[2]; + + //Those a specific values to correct the weights so its more pleasing to the human eye + int gray = (int)(red * 0.3 + green * 0.59 + blue * 0.11); + + pixel[0] = pixel[1] = pixel[2] = (byte)gray; + } + } + } + inputBitmap.UnlockBits(bmpData); + + return inputBitmap; + } + /// + /// Method that binaries the input image up to a certain treshold given + /// + /// the bitmap you want to convert to binary colors + /// The floor at wich the color is considered as white or black + /// The binarised bitmap + public static Bitmap Tresholding(Bitmap inputBitmap, int threshold) + { + Rectangle rect = new Rectangle(0, 0, inputBitmap.Width, inputBitmap.Height); + BitmapData bmpData = inputBitmap.LockBits(rect, ImageLockMode.ReadWrite, inputBitmap.PixelFormat); + int bytesPerPixel = Bitmap.GetPixelFormatSize(inputBitmap.PixelFormat) / 8; + + unsafe + { + byte* ptr = (byte*)bmpData.Scan0.ToPointer(); + int bmpHeight = inputBitmap.Height; + int bmpWidth = inputBitmap.Width; + Parallel.For(0, bmpHeight, y => + { + byte* currentLine = ptr + (y * bmpData.Stride); + for (int x = 0; x < bmpWidth; x++) + { + byte* pixel = currentLine + (x * bytesPerPixel); + + byte blue = pixel[0]; + byte green = pixel[1]; + byte red = pixel[2]; + //Those a specific values to correct the weights so its more pleasing to the human eye + int gray = (int)(red * 0.3 + green * 0.59 + blue * 0.11); + int value = gray < threshold ? 0 : 255; + + pixel[0] = pixel[1] = pixel[2] = (byte)value; + } + }); + } + inputBitmap.UnlockBits(bmpData); + + return inputBitmap; + } + /// + /// Method that removes the pixels that are flagged as background + /// + /// The bitmap you want to remove the background from + /// The Bitmap without the background + public static Bitmap RemoveBG(Bitmap inputBitmap) + { + Rectangle rect = new Rectangle(0, 0, inputBitmap.Width, inputBitmap.Height); + BitmapData bmpData = inputBitmap.LockBits(rect, ImageLockMode.ReadWrite, inputBitmap.PixelFormat); + int bytesPerPixel = Bitmap.GetPixelFormatSize(inputBitmap.PixelFormat) / 8; + + unsafe + { + byte* ptr = (byte*)bmpData.Scan0.ToPointer(); + for (int y = 0; y < inputBitmap.Height; y++) + { + byte* currentLine = ptr + (y * bmpData.Stride); + for (int x = 0; x < inputBitmap.Width; x++) + { + byte* pixel = currentLine + (x * bytesPerPixel); + + int B = pixel[0]; + int G = pixel[1]; + int R = pixel[2]; + + if (R <= F1TV_BACKGROUND_TRESHOLD.R && G <= F1TV_BACKGROUND_TRESHOLD.G && B <= F1TV_BACKGROUND_TRESHOLD.B) + pixel[0] = pixel[1] = pixel[2] = 0; + } + } + } + inputBitmap.UnlockBits(bmpData); + + return inputBitmap; + } + /// + /// Method that removes all the useless things from the image and returns hopefully only the numbers + /// + /// The bitmap you want to remove useless things from (Expects a cropped part of the TyreWindow) + /// The bitmap with (hopefully) only the digits + public unsafe static Bitmap RemoveUseless(Bitmap inputBitmap) + { + //Note you can use something else than a cropped tyre window but I would recommend checking the code first to see if it fits your intended use + Rectangle rect = new Rectangle(0, 0, inputBitmap.Width, inputBitmap.Height); + BitmapData bmpData = inputBitmap.LockBits(rect, ImageLockMode.ReadWrite, inputBitmap.PixelFormat); + int bytesPerPixel = Bitmap.GetPixelFormatSize(inputBitmap.PixelFormat) / 8; + + byte* ptr = (byte*)bmpData.Scan0.ToPointer(); + for (int y = 0; y < inputBitmap.Height; y++) + { + byte* currentLine = ptr + (y * bmpData.Stride); + + List pixelsToRemove = new List(); + + bool fromBorder = true; + + for (int x = 0; x < inputBitmap.Width; x++) + { + byte* pixel = currentLine + (x * bytesPerPixel); + + int B = pixel[0]; + int G = pixel[1]; + int R = pixel[2]; + + if (fromBorder && B < F1TV_BACKGROUND_TRESHOLD.B && G < F1TV_BACKGROUND_TRESHOLD.G && R < F1TV_BACKGROUND_TRESHOLD.R) + { + pixelsToRemove.Add(x); + } + else + { + if (fromBorder) + { + fromBorder = false; + pixelsToRemove.Add(x); + } + } + } + fromBorder = true; + for (int x = inputBitmap.Width - 1; x > 0; x--) + { + byte* pixel = currentLine + (x * bytesPerPixel); + + int B = pixel[0]; + int G = pixel[1]; + int R = pixel[2]; + + if (fromBorder && B < F1TV_BACKGROUND_TRESHOLD.B && G < F1TV_BACKGROUND_TRESHOLD.G && R < F1TV_BACKGROUND_TRESHOLD.R) + { + pixelsToRemove.Add(x); + } + else + { + if (fromBorder) + { + fromBorder = false; + pixelsToRemove.Add(x); + } + } + } + + foreach (int pxPos in pixelsToRemove) + { + byte* pixel = currentLine + (pxPos * bytesPerPixel); + + pixel[0] = 0xFF; + pixel[1] = 0xFF; + pixel[2] = 0xFF; + } + } + + //Removing the color parts + for (int y = 0; y < inputBitmap.Height; y++) + { + byte* currentLine = ptr + (y * bmpData.Stride); + for (int x = 0; x < inputBitmap.Width; x++) + { + byte* pixel = currentLine + (x * bytesPerPixel); + + int B = pixel[0]; + int G = pixel[1]; + int R = pixel[2]; + + if (R >= F1TV_BACKGROUND_TRESHOLD.R + 15 || G >= F1TV_BACKGROUND_TRESHOLD.G + 15 || B >= F1TV_BACKGROUND_TRESHOLD.B + 15) + { + pixel[0] = 0xFF; + pixel[1] = 0xFF; + pixel[2] = 0xFF; + } + } + } + + inputBitmap.UnlockBits(bmpData); + return inputBitmap; + } + /// + /// Recovers the average colors from the Image. NOTE : It wont take in account colors that are lower than the background + /// + /// The bitmap you want to get the average color from + /// The average color of the bitmap + public static Color GetAvgColorFromBitmap(Bitmap inputBitmap) + { + Rectangle rect = new Rectangle(0, 0, inputBitmap.Width, inputBitmap.Height); + BitmapData bmpData = inputBitmap.LockBits(rect, ImageLockMode.ReadWrite, inputBitmap.PixelFormat); + int bytesPerPixel = Bitmap.GetPixelFormatSize(inputBitmap.PixelFormat) / 8; + + int totR = 0; + int totG = 0; + int totB = 0; + + int totPixels = 1; + + unsafe + { + byte* ptr = (byte*)bmpData.Scan0.ToPointer(); + int bmpHeight = inputBitmap.Height; + int bmpWidth = inputBitmap.Width; + Parallel.For(0, bmpHeight, y => + { + byte* currentLine = ptr + (y * bmpData.Stride); + for (int x = 0; x < bmpWidth; x++) + { + byte* pixel = currentLine + (x * bytesPerPixel); + + int B = pixel[0]; + int G = pixel[1]; + int R = pixel[2]; + + if (R >= F1TV_BACKGROUND_TRESHOLD.R || G >= F1TV_BACKGROUND_TRESHOLD.G || B >= F1TV_BACKGROUND_TRESHOLD.B) + { + totPixels++; + totB += pixel[0]; + totG += pixel[1]; + totR += pixel[2]; + } + } + }); + } + inputBitmap.UnlockBits(bmpData); + + return Color.FromArgb(255, Convert.ToInt32((float)totR / (float)totPixels), Convert.ToInt32((float)totG / (float)totPixels), Convert.ToInt32((float)totB / (float)totPixels)); + } + /// + /// This method simply inverts all the colors in a Bitmap + /// + /// the bitmap you want to invert the colors from + /// The bitmap with inverted colors + public static Bitmap InvertColors(Bitmap inputBitmap) + { + Rectangle rect = new Rectangle(0, 0, inputBitmap.Width, inputBitmap.Height); + BitmapData bmpData = inputBitmap.LockBits(rect, ImageLockMode.ReadWrite, inputBitmap.PixelFormat); + int bytesPerPixel = Bitmap.GetPixelFormatSize(inputBitmap.PixelFormat) / 8; + + unsafe + { + byte* ptr = (byte*)bmpData.Scan0.ToPointer(); + for (int y = 0; y < inputBitmap.Height; y++) + { + byte* currentLine = ptr + (y * bmpData.Stride); + for (int x = 0; x < inputBitmap.Width; x++) + { + byte* pixel = currentLine + (x * bytesPerPixel); + + pixel[0] = (byte)(255 - pixel[0]); + pixel[1] = (byte)(255 - pixel[1]); + pixel[2] = (byte)(255 - pixel[2]); + } + } + } + inputBitmap.UnlockBits(bmpData); + + return inputBitmap; + } + /// + /// Methods that applies Bicubic interpolation to increase the size and resolution of an image + /// + /// The bitmap you want to resize + /// The factor of resizing you want to use. I recommend using even numbers + /// The bitmap witht the new size + public static Bitmap Resize(Bitmap inputBitmap, int resizeFactor) + { + var resultBitmap = new Bitmap(inputBitmap.Width * resizeFactor, inputBitmap.Height * resizeFactor); + + using (var graphics = Graphics.FromImage(resultBitmap)) + { + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.DrawImage(inputBitmap, new Rectangle(0, 0, resultBitmap.Width, resultBitmap.Height)); + } + + return resultBitmap; + } + /// + /// method that Highlights the countours of a Bitmap + /// + /// The bitmap you want to highlight the countours of + /// The bitmap with countours highlighted + public static Bitmap HighlightContours(Bitmap inputBitmap) + { + Bitmap outputBitmap = new Bitmap(inputBitmap.Width, inputBitmap.Height); + + Bitmap grayscale = Grayscale(inputBitmap); + Bitmap thresholded = Tresholding(grayscale, 128); + Bitmap dilated = Dilatation(thresholded, 3); + Bitmap eroded = Erode(dilated, 3); + + for (int y = 0; y < inputBitmap.Height; y++) + { + for (int x = 0; x < inputBitmap.Width; x++) + { + Color pixel = inputBitmap.GetPixel(x, y); + Color dilatedPixel = dilated.GetPixel(x, y); + Color erodedPixel = eroded.GetPixel(x, y); + + int gray = (int)(pixel.R * 0.3 + pixel.G * 0.59 + pixel.B * 0.11); + int threshold = dilatedPixel.R; + + if (gray > threshold) + { + outputBitmap.SetPixel(x, y, Color.FromArgb(255, 255, 255)); + } + else if (gray <= threshold && erodedPixel.R == 0) + { + outputBitmap.SetPixel(x, y, Color.FromArgb(255, 0, 0)); + } + else + { + outputBitmap.SetPixel(x, y, Color.FromArgb(0, 0, 0)); + } + } + } + + return outputBitmap; + } + /// + /// Method that that erodes the morphology of a bitmap + /// + /// The bitmap you want to erode + /// The amount of Erosion you want (be carefull its expensive on ressources) + /// The Bitmap with the eroded contents + public static Bitmap Erode(Bitmap inputBitmap, int kernelSize) + { + Bitmap outputBitmap = new Bitmap(inputBitmap.Width, inputBitmap.Height); + + int[,] kernel = new int[kernelSize, kernelSize]; + + for (int i = 0; i < kernelSize; i++) + { + for (int j = 0; j < kernelSize; j++) + { + kernel[i, j] = 1; + } + } + + for (int y = kernelSize / 2; y < inputBitmap.Height - kernelSize / 2; y++) + { + for (int x = kernelSize / 2; x < inputBitmap.Width - kernelSize / 2; x++) + { + bool flag = true; + + for (int i = -kernelSize / 2; i <= kernelSize / 2; i++) + { + for (int j = -kernelSize / 2; j <= kernelSize / 2; j++) + { + Color pixel = inputBitmap.GetPixel(x + i, y + j); + int gray = (int)(pixel.R * 0.3 + pixel.G * 0.59 + pixel.B * 0.11); + + if (gray >= 128 && kernel[i + kernelSize / 2, j + kernelSize / 2] == 1) + { + flag = false; + break; + } + } + + if (!flag) + { + break; + } + } + + if (flag) + { + outputBitmap.SetPixel(x, y, Color.FromArgb(255, 255, 255)); + } + else + { + outputBitmap.SetPixel(x, y, Color.FromArgb(0, 0, 0)); + } + } + } + + return outputBitmap; + } + /// + /// Method that that use dilatation of the morphology of a bitmap + /// + /// The bitmap you want to use dilatation on + /// The amount of dilatation you want (be carefull its expensive on ressources) + /// The Bitmap after Dilatation + public static Bitmap Dilatation(Bitmap inputBitmap, int kernelSize) + { + Bitmap outputBitmap = new Bitmap(inputBitmap.Width, inputBitmap.Height); + + int[,] kernel = new int[kernelSize, kernelSize]; + + for (int i = 0; i < kernelSize; i++) + { + for (int j = 0; j < kernelSize; j++) + { + kernel[i, j] = 1; + } + } + + for (int y = kernelSize / 2; y < inputBitmap.Height - kernelSize / 2; y++) + { + for (int x = kernelSize / 2; x < inputBitmap.Width - kernelSize / 2; x++) + { + bool flag = false; + + for (int i = -kernelSize / 2; i <= kernelSize / 2; i++) + { + for (int j = -kernelSize / 2; j <= kernelSize / 2; j++) + { + Color pixel = inputBitmap.GetPixel(x + i, y + j); + int gray = (int)(pixel.R * 0.3 + pixel.G * 0.59 + pixel.B * 0.11); + + if (gray < 128 && kernel[i + kernelSize / 2, j + kernelSize / 2] == 1) + { + flag = true; + break; + } + } + + if (flag) + { + break; + } + } + + if (flag) + { + outputBitmap.SetPixel(x, y, Color.FromArgb(0, 0, 0)); + } + else + { + outputBitmap.SetPixel(x, y, Color.FromArgb(255, 255, 255)); + } + } + } + + return outputBitmap; + } + } +} + +``` diff --git a/temp_annexes/Code/Program.md b/temp_annexes/Code/Program.md new file mode 100644 index 0000000..e269b1e --- /dev/null +++ b/temp_annexes/Code/Program.md @@ -0,0 +1,27 @@ +# Program.cs + +``` cs +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Test_Merge +{ + internal static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new Form1()); + } + } +} + +``` diff --git a/temp_annexes/Code/Reader.md b/temp_annexes/Code/Reader.md new file mode 100644 index 0000000..407e0f4 --- /dev/null +++ b/temp_annexes/Code/Reader.md @@ -0,0 +1,235 @@ +# Reader.cs + +``` cs +/// Author : Maxime Rohmer +/// Date : 08/05/2023 +/// File : Reader.cs +/// Brief : Class used to Read the config file for the OCR +/// Version : 0.1 + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Drawing; +using System.Windows.Forms; +using System.IO; +using System.Text.Json; + +namespace Test_Merge +{ + public class Reader + { + const int NUMBER_OF_DRIVERS = 20; + public List Drivers; + public List MainZones; + + public Reader(string configFile, Bitmap image,bool loadOCR = true) + { + MainZones = Load(image,configFile,ref Drivers,loadOCR); + } + /// + /// Method that reads the JSON config file and create all the Zones and Windows + /// + /// The image #id on wich you want to create the zones on + public static List Load(Bitmap image,string configFilePath,ref List driverListToFill,bool LoadOCR) + { + List mainZones = new List(); + Bitmap fullImage = image; + List drivers; + Zone mainZone; + + try + { + using (var streamReader = new StreamReader(configFilePath)) + { + var jsonText = streamReader.ReadToEnd(); + var jsonDocument = JsonDocument.Parse(jsonText); + + var driversNames = jsonDocument.RootElement.GetProperty("Drivers"); + driverListToFill = new List(); + + foreach (var nameElement in driversNames.EnumerateArray()) + { + driverListToFill.Add(nameElement.GetString()); + } + + var mainProperty = jsonDocument.RootElement.GetProperty("Main"); + Point MainPosition = new Point(mainProperty.GetProperty("x").GetInt32(), mainProperty.GetProperty("y").GetInt32()); + Size MainSize = new Size(mainProperty.GetProperty("width").GetInt32(), mainProperty.GetProperty("height").GetInt32()); + Rectangle MainRectangle = new Rectangle(MainPosition, MainSize); + mainZone = new Zone(image, MainRectangle,"Main"); + + var zones = mainProperty.GetProperty("Zones"); + var driverZone = zones[0].GetProperty("DriverZone"); + + Point FirstZonePosition = new Point(driverZone.GetProperty("x").GetInt32(), driverZone.GetProperty("y").GetInt32()); + Size FirstZoneSize = new Size(driverZone.GetProperty("width").GetInt32(), driverZone.GetProperty("height").GetInt32()); + + var windows = driverZone.GetProperty("Windows"); + + var driverPosition = windows[0].GetProperty("Position"); + Size driverPositionArea = new Size(driverPosition.GetProperty("width").GetInt32(), FirstZoneSize.Height); + Point driverPositionPosition = new Point(driverPosition.GetProperty("x").GetInt32(), driverPosition.GetProperty("y").GetInt32()); + + var driverGapToLeader = windows[0].GetProperty("GapToLeader"); + Size driverGapToLeaderArea = new Size(driverGapToLeader.GetProperty("width").GetInt32(), FirstZoneSize.Height); + Point driverGapToLeaderPosition = new Point(driverGapToLeader.GetProperty("x").GetInt32(), driverGapToLeader.GetProperty("y").GetInt32()); + + var driverLapTime = windows[0].GetProperty("LapTime"); + Size driverLapTimeArea = new Size(driverLapTime.GetProperty("width").GetInt32(), FirstZoneSize.Height); + Point driverLapTimePosition = new Point(driverLapTime.GetProperty("x").GetInt32(), driverLapTime.GetProperty("y").GetInt32()); + + + var driverDrs = windows[0].GetProperty("DRS"); + Size driverDrsArea = new Size(driverDrs.GetProperty("width").GetInt32(), FirstZoneSize.Height); + Point driverDrsPosition = new Point(driverDrs.GetProperty("x").GetInt32(), driverDrs.GetProperty("y").GetInt32()); + + var driverTyres = windows[0].GetProperty("Tyres"); + Size driverTyresArea = new Size(driverTyres.GetProperty("width").GetInt32(), FirstZoneSize.Height); + Point driverTyresPosition = new Point(driverTyres.GetProperty("x").GetInt32(), driverTyres.GetProperty("y").GetInt32()); + + var driverName = windows[0].GetProperty("Name"); + Size driverNameArea = new Size(driverName.GetProperty("width").GetInt32(), FirstZoneSize.Height); + Point driverNamePosition = new Point(driverName.GetProperty("x").GetInt32(), driverName.GetProperty("y").GetInt32()); + + var driverSector1 = windows[0].GetProperty("Sector1"); + Size driverSector1Area = new Size(driverSector1.GetProperty("width").GetInt32(), FirstZoneSize.Height); + Point driverSector1Position = new Point(driverSector1.GetProperty("x").GetInt32(), driverSector1.GetProperty("y").GetInt32()); + + var driverSector2 = windows[0].GetProperty("Sector2"); + Size driverSector2Area = new Size(driverSector2.GetProperty("width").GetInt32(), FirstZoneSize.Height); + Point driverSector2Position = new Point(driverSector2.GetProperty("x").GetInt32(), driverSector2.GetProperty("y").GetInt32()); + + var driverSector3 = windows[0].GetProperty("Sector3"); + Size driverSector3Area = new Size(driverSector3.GetProperty("width").GetInt32(), FirstZoneSize.Height); + Point driverSector3Position = new Point(driverSector3.GetProperty("x").GetInt32(), driverSector3.GetProperty("y").GetInt32()); + + float offset = (((float)mainZone.ZoneImage.Height - (float)(driverListToFill.Count * FirstZoneSize.Height)) / (float)driverListToFill.Count); + Bitmap MainZoneImage = mainZone.ZoneImage; + List zonesToAdd = new List(); + List zonesImages = new List(); + + for (int i = 0; i < NUMBER_OF_DRIVERS; i++) + { + Point tmpPos = new Point(0, FirstZonePosition.Y + i * FirstZoneSize.Height - Convert.ToInt32(i * offset)); + Zone newDriverZone = new Zone(MainZoneImage, new Rectangle(tmpPos, FirstZoneSize), "DriverZone"); + zonesToAdd.Add(newDriverZone); + zonesImages.Add(newDriverZone.ZoneImage); + + newDriverZone.ZoneImage.Save("Driver"+i+".png"); + } + + //Parallel.For(0, NUMBER_OF_DRIVERS, i => + for (int i = 0; i < NUMBER_OF_DRIVERS; i++) + { + Zone newDriverZone = zonesToAdd[(int)i]; + Bitmap zoneImg = zonesImages[(int)i]; + + newDriverZone.AddWindow(new DriverPositionWindow(zoneImg, new Rectangle(driverPositionPosition, driverPositionArea),LoadOCR)); + newDriverZone.AddWindow(new DriverGapToLeaderWindow(zoneImg, new Rectangle(driverGapToLeaderPosition, driverGapToLeaderArea), LoadOCR)); + newDriverZone.AddWindow(new DriverLapTimeWindow(zoneImg, new Rectangle(driverLapTimePosition, driverLapTimeArea), LoadOCR)); + newDriverZone.AddWindow(new DriverDrsWindow(zoneImg, new Rectangle(driverDrsPosition, driverDrsArea), LoadOCR)); + newDriverZone.AddWindow(new DriverTyresWindow(zoneImg, new Rectangle(driverTyresPosition, driverTyresArea), LoadOCR)); + newDriverZone.AddWindow(new DriverNameWindow(zoneImg, new Rectangle(driverNamePosition, driverNameArea), LoadOCR)); + newDriverZone.AddWindow(new DriverSectorWindow(zoneImg, new Rectangle(driverSector1Position, driverSector1Area),1, LoadOCR)); + newDriverZone.AddWindow(new DriverSectorWindow(zoneImg, new Rectangle(driverSector2Position, driverSector2Area),2, LoadOCR)); + newDriverZone.AddWindow(new DriverSectorWindow(zoneImg, new Rectangle(driverSector3Position, driverSector3Area),3, LoadOCR)); + + mainZone.AddZone(newDriverZone); + }//); + //MessageBox.Show("We have a main zone with " + MainZone.Zones.Count() + " Driver zones with " + MainZone.Zones[4].Windows.Count() + " windows each and we have " + Drivers.Count + " drivers"); + mainZones.Add(mainZone); + } + } + catch (IOException ex) + { + MessageBox.Show("Error reading JSON file: " + ex.Message); + } + catch (JsonException ex) + { + MessageBox.Show("Invalid JSON format: " + ex.Message); + } + return mainZones; + } + /// + /// Method that calls all the zones and windows to get the content they can find on the image to display them + /// + /// The id of the image we are working with + /// a string representation of all the returns + public async Task Decode(List mainZones,List drivers) + { + string result = ""; + List mainResults = new List(); + + //Decode + for (int mainZoneId = 0; mainZoneId < mainZones.Count; mainZoneId++) + { + switch (mainZoneId) + { + case 0: + //Main Zone + foreach (Zone z in mainZones[mainZoneId].Zones) + { + mainResults.Add(await z.Decode(Drivers)); + } + break; + //Next there could be a Title Zone and TrackInfoZone + } + } + + //Display + foreach (DriverData driver in mainResults) + { + result += driver.ToString(); + result += Environment.NewLine; + } + + return result; + } + /// + /// Method that can be used to convert an amount of miliseconds into a more readable human form + /// + /// The given amount of miliseconds ton convert + /// A human readable string that represents the ms + public static string ConvertMsToTime(int amountOfMs) + { + //Convert.ToInt32 would round upand I dont want that + int minuts = (int)((float)amountOfMs / (1000f * 60f)); + int seconds = (int)((amountOfMs - (minuts * 60f * 1000f)) / 1000); + int ms = amountOfMs - ((minuts * 60 * 1000) + (seconds * 1000)); + + return minuts + ":" + seconds.ToString("00") + ":" + ms.ToString("000"); + } + /// + /// Old method that can draw on an image where the windows and zones are created. mostly used for debugging + /// + /// the #id of the image we are working with + /// the drawed bitmap + public Bitmap Draw(Bitmap image,List mainZones) + { + + Graphics g = Graphics.FromImage(image); + + foreach (Zone z in mainZones) + { + int count = 0; + foreach (Zone zz in z.Zones) + { + g.DrawRectangle(Pens.Red, z.Bounds); + foreach (Window w in zz.Windows) + { + g.DrawRectangle(Pens.Blue, new Rectangle(z.Bounds.X + zz.Bounds.X, z.Bounds.Y + zz.Bounds.Y, zz.Bounds.Width, zz.Bounds.Height)); + } + + count++; + } + } + + return image; + } + } +} + +``` diff --git a/temp_annexes/Code/Settings.md b/temp_annexes/Code/Settings.md new file mode 100644 index 0000000..38f9dda --- /dev/null +++ b/temp_annexes/Code/Settings.md @@ -0,0 +1,420 @@ +# Settings.cs + +``` cs +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.IO; + +namespace Test_Merge +{ + public partial class Settings : Form + { + private string _grandPrixUrl = ""; + private string _grandPrixName = ""; + private int _grandPrixYear = 2000; + private List _driverList = new List(); + + private F1TVEmulator Emulator = null; + private ConfigurationTool Config = null; + + private bool CreatingZone = false; + private Point ZoneP1; + private Point ZoneP2; + + private bool CreatingWindow = false; + private Point WindowP1; + private Point WindowP2; + + List WindowsToAdd = new List(); + + public string GrandPrixUrl { get => _grandPrixUrl; private set => _grandPrixUrl = value; } + public string GrandPrixName { get => _grandPrixName; private set => _grandPrixName = value; } + public int GrandPrixYear { get => _grandPrixYear; private set => _grandPrixYear = value; } + public List DriverList { get => _driverList; private set => _driverList = value; } + + public Settings() + { + InitializeComponent(); + Load(); + } + private void Load() + { + RefreshUI(); + } + private void RefreshUI() + { + + lsbDrivers.DataSource = null; + lsbDrivers.DataSource = DriverList; + + if (Directory.Exists(ConfigurationTool.CONFIGS_FOLDER_NAME)) + { + lsbPresets.DataSource = null; + lsbPresets.DataSource = Directory.GetFiles(ConfigurationTool.CONFIGS_FOLDER_NAME); + } + if (CreatingZone) + { + if (ZoneP1 == new Point(-1, -1)) + { + lblZonePointsRemaning.Text = "2 points Remaining"; + } + else + { + lblZonePointsRemaning.Text = "1 point Remaining"; + } + } + else + { + lblZonePointsRemaning.Text = ""; + } + + if (CreatingWindow) + { + if (WindowP1 == new Point(-1, -1)) + { + lblWindowPointsRemaining.Text = "2 points Remaining"; + } + else + { + lblWindowPointsRemaining.Text = "1 point Remaining"; + } + lblWindowPointsRemaining.Text = ConfigurationTool.NUMBER_OF_ZONES - WindowsToAdd.Count() + " Windows remaining"; + } + else + { + lblWindowPointsRemaining.Text = ""; + lblWindowsRemaining.Text = ""; + } + if (Config != null) + { + pbxMain.Image = Config.MainZone.Draw(); + if(Config.MainZone.Zones.Count > 0) + pbxDriverZone.Image = Config.MainZone.Zones[0].Draw(); + } + } + private void CreateNewZone(Point p1, Point p2) + { + Rectangle dimensions = CreateAbsoluteRectangle(p1, p2); + Config = new ConfigurationTool((Bitmap)pbxMain.Image, dimensions); + RefreshUI(); + } + private void CreateWindows(List dimensions) + { + if (Config != null) + { + Config.AddWindows(dimensions); + } + } + private void tbxGpUrl_TextChanged(object sender, EventArgs e) + { + GrandPrixUrl = tbxGpUrl.Text; + } + + private void tbxGpName_TextChanged(object sender, EventArgs e) + { + GrandPrixName = tbxGpName.Text; + } + + private void tbxGpYear_TextChanged(object sender, EventArgs e) + { + int year; + try + { + year = Convert.ToInt32(tbxGpYear.Text); + } + catch + { + year = 1545; + } + GrandPrixYear = year; + } + + private void btnAddDriver_Click(object sender, EventArgs e) + { + string newDriver = tbxDriverName.Text; + DriverList.Add(newDriver); + tbxDriverName.Text = ""; + RefreshUI(); + } + + private void btnRemoveDriver_Click(object sender, EventArgs e) + { + if (lsbDrivers.SelectedIndex >= 0) + { + DriverList.RemoveAt(lsbDrivers.SelectedIndex); + } + RefreshUI(); + } + private void SwitchZoneCreation() + { + if (CreatingZone) + { + CreatingZone = false; + lblZonePointsRemaning.Text = ""; + } + else + { + CreatingZone = true; + + if (Config != null) + Config.ResetMainZone(); + + if (CreatingWindow) + SwitchWindowCreation(); + + if (Emulator != null && Emulator.Ready) + { + Config = null; + pbxMain.Image = Emulator.Screenshot(); + } + + ZoneP1 = new Point(-1, -1); + ZoneP2 = new Point(-1, -1); + + lblZonePointsRemaning.Text = "2 Points left"; + } + RefreshUI(); + } + private void SwitchWindowCreation() + { + if (CreatingWindow) + { + CreatingWindow = false; + } + else + { + CreatingWindow = true; + + if (Config != null) + Config.ResetWindows(); + + if (CreatingZone) + SwitchZoneCreation(); + + WindowP1 = new Point(-1, -1); + WindowP2 = new Point(-1, -1); + + WindowsToAdd = new List(); + } + RefreshUI(); + } + private void btnCreatZone_Click(object sender, EventArgs e) + { + SwitchZoneCreation(); + } + private void btnCreateWindow_Click(object sender, EventArgs e) + { + SwitchWindowCreation(); + } + private void pbxMain_MouseClick(object sender, MouseEventArgs e) + { + if (CreatingZone && pbxMain.Image != null) + { + //Point coordinates = pbxMain.PointToClient(new Point(MousePosition.X, MousePosition.Y)); + Point coordinates = e.Location; + float xOffset = (float)pbxMain.Image.Width / (float)pbxMain.Width; + float yOffset = (float)pbxMain.Image.Height / (float)pbxMain.Height; + Point newPoint = new Point(Convert.ToInt32((float)coordinates.X * xOffset), Convert.ToInt32((float)coordinates.Y * yOffset)); + + //MessageBox.Show("Coordinates" + Environment.NewLine + "Old : " + coordinates.ToString() + Environment.NewLine + "New : " + newPoint.ToString()); + + if (ZoneP1 == new Point(-1, -1)) + { + ZoneP1 = newPoint; + } + else + { + ZoneP2 = newPoint; + CreateNewZone(ZoneP1, ZoneP2); + SwitchZoneCreation(); + } + RefreshUI(); + } + } + private void pbxMain_Click(object sender, EventArgs e) + { + //Not the right one to use visibly + } + private void pbxDriverZone_MouseClick(object sender, MouseEventArgs e) + { + if (CreatingWindow && pbxDriverZone.Image != null) + { + Point coordinates = e.Location; + + float xOffset = (float)pbxDriverZone.Image.Width / (float)pbxDriverZone.Width; + float yOffset = (float)pbxDriverZone.Image.Height / (float)pbxDriverZone.Height; + + Point newPoint = new Point(Convert.ToInt32((float)coordinates.X * xOffset), Convert.ToInt32((float)coordinates.Y * yOffset)); + + if (WindowP1 == new Point(-1, -1)) + { + WindowP1 = newPoint; + } + else + { + WindowP2 = newPoint; + WindowsToAdd.Add(CreateAbsoluteRectangle(WindowP1, WindowP2)); + + if (WindowsToAdd.Count < ConfigurationTool.NUMBER_OF_ZONES) + { + WindowP1 = new Point(-1, -1); + WindowP2 = new Point(-1, -1); + } + else + { + WindowP1 = new Point(WindowP1.X, 0); + WindowP2 = new Point(WindowP2.X, pbxDriverZone.Image.Height); + CreateWindows(WindowsToAdd); + SwitchWindowCreation(); + } + } + RefreshUI(); + } + } + private void pbxDriverZone_Click(object sender, EventArgs e) + { + //Not the right one to use visibly + } + private Rectangle CreateAbsoluteRectangle(Point p1, Point p2) + { + Point newP1 = new Point(); + Point newP2 = new Point(); + + if (p1.X < p2.X) + { + newP1.X = p1.X; + newP2.X = p2.X; + } + else + { + newP1.X = p2.X; + newP2.X = p1.X; + } + + if (p1.Y < p2.Y) + { + newP1.Y = p1.Y; + newP2.Y = p2.Y; + } + else + { + newP1.Y = p2.Y; + newP2.Y = p1.Y; + } + return new Rectangle(newP1.X, newP1.Y, newP2.X - newP1.X, newP2.Y - newP1.Y); + } + + private async void btnRefresh_Click(object sender, EventArgs e) + { + btnRefresh.Enabled = false; + if (Emulator == null || Emulator.GrandPrixUrl != tbxGpUrl.Text) + { + Emulator = new F1TVEmulator(tbxGpUrl.Text); + } + + if (!Emulator.Ready) + { + Task start = Task.Run(() => Emulator.Start()); + int errorCode = await start; + if (errorCode != 0) + { + string message; + switch (errorCode) + { + case 101: + message = "Error " + errorCode + " Could not start the driver. It could be because an other instance is runnin make sure you closed them all before trying again"; + break; + case 102: + message = "Error " + errorCode + " Could not navigate on the F1TV site. Make sure the correct URL has been given and that you logged from chrome. It can take a few minutes to update"; + break; + case 103: + message = "Error " + errorCode + " The url is not a valid url"; + break; + case 104: + message = "Error " + errorCode + " The url is not a valid url"; + break; + case 105: + message = "Error " + errorCode + " There has been an error trying to emulate button presses. Please try again"; + break; + case 106: + message = "Error " + errorCode + " There has been an error trying to emulate button presses. Please try again"; + break; + default: + message = "Could not start the emulator Error " + errorCode; + break; + } + MessageBox.Show(message); + } + else + { + pbxMain.Image = Emulator.Screenshot(); + } + } + else + { + pbxMain.Image = Emulator.Screenshot(); + } + btnRefresh.Enabled = true; + } + + private void Settings_FormClosing(object sender, FormClosingEventArgs e) + { + if (Emulator != null) + { + Emulator.Stop(); + } + } + + private void btnResetDriver_Click(object sender, EventArgs e) + { + if (Emulator != null) + { + Emulator.ResetDriver(); + } + } + + private void btnSavePreset_Click(object sender, EventArgs e) + { + string presetName = tbxPresetName.Text; + if (Config != null) + { + Config.SaveToJson(DriverList,presetName); + } + RefreshUI(); + } + + private void lsbPresets_SelectedIndexChanged(object sender, EventArgs e) + { + //Nothing + } + + private void btnLoadPreset_Click(object sender, EventArgs e) + { + if (lsbPresets.SelectedIndex >= 0 && pbxMain.Image != null) + { + try + { + Reader reader = new Reader(lsbPresets.Items[lsbPresets.SelectedIndex].ToString(), (Bitmap)pbxMain.Image,false); + //MainZones #0 is the big main zone containing driver zones + Config = new ConfigurationTool((Bitmap)pbxMain.Image, reader.MainZones[0].Bounds); + Config.MainZone = reader.MainZones[0]; + DriverList = reader.Drivers; + } + catch (Exception ex) + { + MessageBox.Show("Could not load the settings error :" + ex); + } + RefreshUI(); + } + } + } +} + +``` diff --git a/temp_annexes/Code/Window.md b/temp_annexes/Code/Window.md new file mode 100644 index 0000000..f453494 --- /dev/null +++ b/temp_annexes/Code/Window.md @@ -0,0 +1,322 @@ +# Window.cs + +``` cs +/// Author : Maxime Rohmer +/// Date : 08/05/2023 +/// File : Window.cs +/// Brief : Default Window object that is mainly expected to be inherited. +/// Version : 0.1 + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Drawing; +using System.IO; +using Tesseract; +using System.Text.RegularExpressions; +using System.Drawing.Drawing2D; + +namespace Test_Merge +{ + public class Window + { + private Rectangle _bounds; + private Bitmap _image; + private string _name; + protected TesseractEngine Engine; + public Rectangle Bounds { get => _bounds; private set => _bounds = value; } + public Bitmap Image { get => _image; set => _image = value; } + public string Name { get => _name; protected set => _name = value; } + //This will have to be changed if you want to make it run on your machine + public static DirectoryInfo TESS_DATA_FOLDER = new DirectoryInfo(@"C:\Users\Moi\Pictures\SeleniumScreens\TessData"); + + public Bitmap WindowImage + { + get + { + //This little trickery lets you have the image that the window sees + Bitmap sample = new Bitmap(Bounds.Width, Bounds.Height); + Graphics g = Graphics.FromImage(sample); + g.DrawImage(Image, new Rectangle(0, 0, sample.Width, sample.Height), Bounds, GraphicsUnit.Pixel); + return sample; + } + } + public Window(Bitmap image, Rectangle bounds, bool generateEngine = true) + { + Image = image; + Bounds = bounds; + if (generateEngine) + { + Engine = new TesseractEngine(TESS_DATA_FOLDER.FullName, "eng", EngineMode.Default); + Engine.DefaultPageSegMode = PageSegMode.SingleLine; + } + } + /// + /// Method that will have to be used by the childrens to let the model make them decode the images they have + /// + /// Returns an object because we dont know what kind of return it will be + public virtual async Task DecodePng() + { + return "NaN"; + } + /// + /// Method that will have to be used by the childrens to let the model make them decode the images they have + /// + /// This is a list of the different possible drivers in the race. It should not be too big but NEVER be too short + /// Returns an object because we dont know what kind of return it will be + public virtual async Task DecodePng(List driverList) + { + return "NaN"; + } + /// + /// This converts an image into a byte[]. It can be usefull when doing unsafe stuff. Use at your own risks + /// + /// The image you want to convert + /// A byte array containing the image informations + public static byte[] ImageToByte(Image inputImage) + { + using (var stream = new MemoryStream()) + { + inputImage.Save(stream, System.Drawing.Imaging.ImageFormat.Png); + return stream.ToArray(); + } + } + /// + /// This method is used to recover a time from a PNG using Tesseract OCR + /// + /// The image where the text is + /// The type of window it is + /// The Tesseract Engine + /// The time in milliseconds + public static async Task GetTimeFromPng(Bitmap windowImage, OcrImage.WindowType windowType, TesseractEngine Engine) + { + //Kind of a big method but it has a lot of error handling and has to work with three special cases + string rawResult = ""; + int result = 0; + + switch (windowType) + { + case OcrImage.WindowType.Sector: + //The usual sector is in this form : 33.456 + Engine.SetVariable("tessedit_char_whitelist", "0123456789."); + break; + case OcrImage.WindowType.LapTime: + //The usual Lap time is in this form : 1:45:345 + Engine.SetVariable("tessedit_char_whitelist", "0123456789.:"); + break; + case OcrImage.WindowType.Gap: + //The usual Gap is in this form : + 34.567 + Engine.SetVariable("tessedit_char_whitelist", "0123456789.+"); + break; + default: + Engine.SetVariable("tessedit_char_whitelist", ""); + break; + } + + + Bitmap enhancedImage = new OcrImage(windowImage).Enhance(windowType); + + var tessImage = Pix.LoadFromMemory(ImageToByte(enhancedImage)); + + Page page = Engine.Process(tessImage); + Graphics g = Graphics.FromImage(enhancedImage); + // Get the iterator for the page layout + using (var iter = page.GetIterator()) + { + // Loop over the elements of the page layout + iter.Begin(); + do + { + // Get the text for the current element + try + { + rawResult += iter.GetText(PageIteratorLevel.Word); + } + catch + { + //nothing we just dont add it if its not a number + } + } while (iter.Next(PageIteratorLevel.Word)); + } + + List rawNumbers; + + //In the gaps we can find '+' but we dont care about it its redondant a driver will never be - something + if (windowType == OcrImage.WindowType.Gap) + rawResult = Regex.Replace(rawResult, "[^0-9.:]", ""); + + //Splits into minuts seconds miliseconds + rawNumbers = rawResult.Split('.', ':').ToList(); + //removes any empty cells (tho this usually sign of a really bad OCR implementation tbh will have to be fixed higher in the chian) + rawNumbers.RemoveAll(x => ((string)x) == ""); + + if (rawNumbers.Count == 3) + { + //mm:ss:ms + result = (Convert.ToInt32(rawNumbers[0]) * 1000 * 60) + (Convert.ToInt32(rawNumbers[1]) * 1000) + Convert.ToInt32(rawNumbers[2]); + } + else + { + if (rawNumbers.Count == 2) + { + //ss:ms + result = (Convert.ToInt32(rawNumbers[0]) * 1000) + Convert.ToInt32(rawNumbers[1]); + + if (result > 999999) + { + //We know that we have way too much seconds to make a minut + //Its usually because the ":" have been interpreted as a number + int minuts = (int)(rawNumbers[0][0] - '0'); + // rawNumbers[0][1] should contain the : that has been mistaken + int seconds = Convert.ToInt32(rawNumbers[0][2].ToString() + rawNumbers[0][3].ToString()); + int ms = Convert.ToInt32(rawNumbers[1]); + result = (Convert.ToInt32(minuts) * 1000 * 60) + (Convert.ToInt32(seconds) * 1000) + Convert.ToInt32(ms); + } + } + else + { + if (rawNumbers.Count == 1) + { + try + { + result = Convert.ToInt32(rawNumbers[0]); + } + catch + { + //It can be because the input is empty or because its the LEADER bracket + result = 0; + } + } + else + { + //Auuuugh + result = 0; + } + } + } + page.Dispose(); + return result; + } + /// + /// Method that recovers strings from an image using Tesseract OCR + /// + /// The image of the window that contains text + /// The Tesseract engine + /// The list of allowed chars + /// The type of window the text is on. Depending on the context the OCR will behave differently + /// the string it found + public static async Task GetStringFromPng(Bitmap WindowImage, TesseractEngine Engine, string allowedChars = "", OcrImage.WindowType windowType = OcrImage.WindowType.Text) + { + string result = ""; + + Engine.SetVariable("tessedit_char_whitelist", allowedChars); + + Bitmap rawData = WindowImage; + Bitmap enhancedImage = new OcrImage(rawData).Enhance(windowType); + + Page page = Engine.Process(enhancedImage); + using (var iter = page.GetIterator()) + { + iter.Begin(); + do + { + result += iter.GetText(PageIteratorLevel.Word); + } while (iter.Next(PageIteratorLevel.Word)); + } + page.Dispose(); + return result; + } + /// + /// Get a smaller image from a bigger one + /// + /// The big bitmap you want to get a part of + /// The dimensions of the new bitmap + /// The little bitmap + protected Bitmap GetSmallBitmapFromBigOne(Bitmap inputBitmap, Rectangle newBitmapDimensions) + { + Bitmap sample = new Bitmap(newBitmapDimensions.Width, newBitmapDimensions.Height); + Graphics g = Graphics.FromImage(sample); + g.DrawImage(inputBitmap, new Rectangle(0, 0, sample.Width, sample.Height), newBitmapDimensions, GraphicsUnit.Pixel); + return sample; + } + /// + /// Returns the closest string from a list of options + /// + /// an array of all the possibilities + /// the string you want to compare + /// The closest option + protected static string FindClosestMatch(List options, string testString) + { + var closestMatch = ""; + var closestDistance = int.MaxValue; + + foreach (var item in options) + { + var distance = LevenshteinDistance(item, testString); + if (distance < closestDistance) + { + closestMatch = item; + closestDistance = distance; + } + } + return closestMatch; + } + //This method has been generated with the help of ChatGPT + /// + /// Method that computes a score of distance between two strings + /// + /// The first string (order irrelevant) + /// The second string (order irrelevant) + /// The levenshtein distance + protected static int LevenshteinDistance(string string1, string string2) + { + if (string.IsNullOrEmpty(string1)) + { + return string.IsNullOrEmpty(string2) ? 0 : string2.Length; + } + + if (string.IsNullOrEmpty(string2)) + { + return string.IsNullOrEmpty(string1) ? 0 : string1.Length; + } + + var d = new int[string1.Length + 1, string2.Length + 1]; + for (var i = 0; i <= string1.Length; i++) + { + d[i, 0] = i; + } + + for (var j = 0; j <= string2.Length; j++) + { + d[0, j] = j; + } + + for (var i = 1; i <= string1.Length; i++) + { + for (var j = 1; j <= string2.Length; j++) + { + var cost = (string1[i - 1] == string2[j - 1]) ? 0 : 1; + d[i, j] = Math.Min(Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1), d[i - 1, j - 1] + cost); + } + } + + return d[string1.Length, string2.Length]; + } + public virtual string ToJSON() + { + string result = ""; + + result += "\"" + Name + "\"" + ":{" + Environment.NewLine; + result += "\t" + "\"x\":" + Bounds.X + "," + Environment.NewLine; + result += "\t" + "\"y\":" + Bounds.Y + "," + Environment.NewLine; + result += "\t" + "\"width\":" + Bounds.Width + Environment.NewLine; + result += "}"; + + return result; + } + } +} + +``` diff --git a/temp_annexes/Code/Zone.md b/temp_annexes/Code/Zone.md new file mode 100644 index 0000000..6df76a3 --- /dev/null +++ b/temp_annexes/Code/Zone.md @@ -0,0 +1,242 @@ +# Zone.cs + +``` cs +/// Author : Maxime Rohmer +/// Date : 08/05/2023 +/// File : Zone.cs +/// Brief : Class that contains all the methods and infos for a zone. This is designed to be potentially be inherited. +/// Version : 0.1 + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Test_Merge +{ + public class Zone + { + private Rectangle _bounds; + private List _zones; + private List _windows; + private Bitmap _image; + private string _name; + + public Bitmap ZoneImage + { + get + { + //This little trickery lets you have the image that the zone sees + Bitmap sample = new Bitmap(Bounds.Width, Bounds.Height); + Graphics g = Graphics.FromImage(sample); + g.DrawImage(Image, new Rectangle(0, 0, sample.Width, sample.Height), Bounds, GraphicsUnit.Pixel); + return sample; + } + } + public Bitmap Image + { + get { return _image; } + set + { + //It automatically sets the image for the contained windows and zones + _image = Image; + foreach (Window w in Windows) + { + w.Image = ZoneImage; + } + foreach (Zone z in Zones) + { + z.Image = Image; + } + } + } + + public Rectangle Bounds { get => _bounds; protected set => _bounds = value; } + public List Zones { get => _zones; protected set => _zones = value; } + public List Windows { get => _windows; protected set => _windows = value; } + public string Name { get => _name; protected set => _name = value; } + + public Zone(Bitmap image, Rectangle bounds, string name) + { + Windows = new List(); + Zones = new List(); + Name = name; + + //You cant set the image in the CTOR because the processing is impossible at first initiation + _image = image; + Bounds = bounds; + } + /// + /// Adds a zone to the list of zones + /// + /// The zone you want to add + public virtual void AddZone(Zone zone) + { + Zones.Add(zone); + } + /// + /// Add a window to the list of windows + /// + /// the window you want to add + public virtual void AddWindow(Window window) + { + Windows.Add(window); + } + /// + /// Calls all the windows to do OCR and to give back the results so we can send them to the model + /// + /// A list of all the driver in the race to help with text recognition + /// A driver data object that contains all the infos about a driver + public virtual async Task Decode(List driverList) + { + int sectorCount = 0; + DriverData result = new DriverData(); + Parallel.ForEach(Windows, async w => + { + // A switch would be prettier but I dont think its supported in this C# version + if (w is DriverNameWindow) + result.Name = (string)await (w as DriverNameWindow).DecodePng(driverList); + if (w is DriverDrsWindow) + result.DRS = (bool)await (w as DriverDrsWindow).DecodePng(); + if (w is DriverGapToLeaderWindow) + result.GapToLeader = (int)await (w as DriverGapToLeaderWindow).DecodePng(); + if (w is DriverLapTimeWindow) + result.LapTime = (int)await (w as DriverLapTimeWindow).DecodePng(); + if (w is DriverPositionWindow) + result.Position = (int)await (w as DriverPositionWindow).DecodePng(); + if (w is DriverSectorWindow) + { + sectorCount++; + if (sectorCount == 1) + result.Sector1 = (int)await (w as DriverSectorWindow).DecodePng(); + if (sectorCount == 2) + result.Sector2 = (int)await (w as DriverSectorWindow).DecodePng(); + if (sectorCount == 3) + result.Sector3 = (int)await (w as DriverSectorWindow).DecodePng(); + } + if (w is DriverTyresWindow) + result.CurrentTyre = (Tyre)await (w as DriverTyresWindow).DecodePng(); + }); + return result; + } + public virtual Bitmap Draw() + { + Bitmap img; + + //If its the main zone we want to see everything + if (Zones.Count > 0) + { + img = Image; + } + else + { + img = ZoneImage; + } + + Graphics g = Graphics.FromImage(img); + + //If its the main zone we need to visualize the Zone bounds displayed + if (Zones.Count > 0) + g.DrawRectangle(new Pen(Brushes.Violet, 5), Bounds); + + foreach (Zone z in Zones) + { + Rectangle newBounds = new Rectangle(z.Bounds.X, z.Bounds.Y + Bounds.Y, z.Bounds.Width, z.Bounds.Height); + g.DrawRectangle(Pens.Red, newBounds); + } + foreach (Window w in Windows) + { + g.DrawRectangle(Pens.Blue, w.Bounds); + } + return img; + } + public void ResetZones() + { + Zones.Clear(); + } + public void ResetWindows() + { + foreach (Zone z in Zones) + { + z.ResetWindows(); + } + Windows.Clear(); + } + public virtual string ToJSON() + { + string result = ""; + result += "\"" + Name + "\":{" + Environment.NewLine; + result += "\t" + "\"x\":" + Bounds.X + "," + Environment.NewLine; + result += "\t" + "\"y\":" + Bounds.Y + "," + Environment.NewLine; + result += "\t" + "\"width\":" + Bounds.Width + "," + Environment.NewLine; + result += "\t" + "\"height\":" + Bounds.Height; + + if (Windows.Count != 0) + { + result += "," + Environment.NewLine; + + result += "\t" + "\"Windows\":[" + Environment.NewLine; + result += "\t\t{" + Environment.NewLine; + int Wcount = 0; + foreach (Window w in Windows) + { + result += "\t\t" + w.ToJSON(); + Wcount++; + if (Wcount != Windows.Count) + result += ","; + } + result += "\t\t}" + Environment.NewLine; + result += "\t" + "]" + Environment.NewLine; + } + else + { + result += Environment.NewLine; + } + if (Zones.Count != 0) + { + result += "," + Environment.NewLine; + + result += "\t" + "\"Zones\":[" + Environment.NewLine; + result += "\t\t{" + Environment.NewLine; + int Zcount = 0; + //foreach (Zone z in Zones) + //{ + result += "\t\t" + Zones[0].ToJSON(); + Zcount++; + if (Zcount != Zones.Count) + //result += ","; + //} + result += "\t\t}" + Environment.NewLine; + result += "\t" + "]" + Environment.NewLine; + } + else + { + result += Environment.NewLine; + } + + result += "}"; + + return result; + } + /// + /// Checks if the given Rectangle fits in the current zone + /// + /// The Rectangle you want to check the fittment + /// + protected bool Fits(Rectangle inputRectangle) + { + if (inputRectangle.X + inputRectangle.Width > Bounds.Width || inputRectangle.Y + inputRectangle.Height > Bounds.Height || inputRectangle.X < 0 || inputRectangle.Y < 0) + { + return false; + } + else + { + return true; + } + } + } +} + +``` diff --git a/temp_annexes/Code/recoverCookiesCSV.md b/temp_annexes/Code/recoverCookiesCSV.md new file mode 100644 index 0000000..33341f6 --- /dev/null +++ b/temp_annexes/Code/recoverCookiesCSV.md @@ -0,0 +1,88 @@ +# recoverCookiesCSV.py + +``` py +# Rohmer Maxime +# RecoverCookies.py +# Little script that recovers the cookies stored in the chrome sqlite database and then decrypts them using the key stored in the chrome files +# This script has been created to be used by an other programm or for the data to not be used directly. This is why it stores all the decoded cookies in a csv. (Btw could be smart for the end programm to delete the csv after using it) +# Parts of this cript have been created with the help of ChatGPT + +import os +import json +import base64 +import sqlite3 +import win32crypt +from Cryptodome.Cipher import AES +from pathlib import Path +import csv + +def get_master_key(): + with open( + os.getenv("localappdata") + "\\Google\\Chrome\\User Data\\Local State", "r" + ) as f: + local_state = f.read() + local_state = json.loads(local_state) + master_key = base64.b64decode(local_state["os_crypt"]["encrypted_key"]) + master_key = master_key[5:] # removing DPAPI + master_key = win32crypt.CryptUnprotectData(master_key, None, None, None, 0)[1] + print("MASTER KEY :") + print(master_key) + print(len(master_key)) + return master_key + +def decrypt_payload(cipher, payload): + return cipher.decrypt(payload) + +def generate_cipher(aes_key, iv): + return AES.new(aes_key, AES.MODE_GCM, iv) + +def decrypt_password(buff, master_key): + try: + iv = buff[3:15] + payload = buff[15:] + cipher = generate_cipher(master_key, iv) + decrypted_pass = decrypt_payload(cipher, payload) + decrypted_pass = decrypted_pass[:-16].decode() # remove suffix bytes + return decrypted_pass + except Exception: + # print("Probably saved password from Chrome version older than v80\n") + # print(str(e)) + return "Chrome < 80" + + +master_key = get_master_key() + +cookies_path = Path( + os.getenv("localappdata") + "\\Google\\Chrome\\User Data\\Default\\Network\\Cookies" +) + +if not cookies_path.exists(): + raise ValueError("Cookies file not found") + +with sqlite3.connect(cookies_path) as connection: + connection.row_factory = sqlite3.Row + cursor = connection.cursor() + cursor.execute("SELECT * FROM cookies") + + with open('cookies.csv', 'a', newline='') as csvfile: + fieldnames = ['host_key', 'name', 'value', 'path', 'expires_utc', 'is_secure', 'is_httponly'] + writer = csv.DictWriter(csvfile, fieldnames=fieldnames) + + if csvfile.tell() == 0: + writer.writeheader() + + for row in cursor.fetchall(): + decrypted_value = decrypt_password(row["encrypted_value"], master_key) + writer.writerow({ + 'host_key': row["host_key"], + 'name': row["name"], + 'value': decrypted_value, + 'path': row["path"], + 'expires_utc': row["expires_utc"], + 'is_secure': row["is_secure"], + 'is_httponly': row["is_httponly"] + }) + +print("Finished CSV") + +```