From a70ef2cf2e01595c05380e5bdc1d3d2a5c202ed5 Mon Sep 17 00:00:00 2001 From: Animesh404 Date: Wed, 19 Jun 2024 20:58:01 +0530 Subject: [PATCH 1/9] extracting difference between two images --- experiments/extract_difference_image.py | 45 ++++++++++++++++++++++++ experiments/winCalNew.png | Bin 0 -> 16821 bytes experiments/winCalOld.png | Bin 0 -> 21555 bytes 3 files changed, 45 insertions(+) create mode 100644 experiments/extract_difference_image.py create mode 100644 experiments/winCalNew.png create mode 100644 experiments/winCalOld.png diff --git a/experiments/extract_difference_image.py b/experiments/extract_difference_image.py new file mode 100644 index 000000000..8ba39ea3b --- /dev/null +++ b/experiments/extract_difference_image.py @@ -0,0 +1,45 @@ +from PIL import Image +import numpy as np +import cv2 +from skimage.metrics import structural_similarity as ssim + +def extract_difference_image( + new_image: Image.Image, + old_image: Image.Image, + tolerance: float = 0.05, +) -> Image.Image: + """Extract the portion of the new image that is different from the old image.""" + new_image_np = np.array(new_image.convert('L')) + old_image_np = np.array(old_image.convert('L')) + + # Compute the SSIM between the two images + score, diff = ssim(new_image_np, old_image_np, full=True) + diff = (diff * 255).astype("uint8") + + # Threshold the difference image to get the regions that are different + thresh = cv2.threshold(diff, 255 * (1 - tolerance), 255, cv2.THRESH_BINARY_INV)[1] + + # Find contours of the different regions + contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + + # Create a mask of the differences + mask = np.zeros_like(new_image_np) + cv2.drawContours(mask, contours, -1, (255), thickness=cv2.FILLED) + + # Apply the mask to the new image to extract the different regions + diff_image_np = cv2.bitwise_and(np.array(new_image), np.array(new_image), mask=mask) + + return Image.fromarray(diff_image_np) + +# Example usage: +# new_image = Image.open('path_to_new_image') +# old_image = Image.open('path_to_old_image') +# difference_image = extract_difference_image(new_image, old_image, tolerance=0.05) +# difference_image.show() + +new_image = Image.open('./winCalNew.png') +old_image = Image.open('./winCalOld.png') +difference_image = extract_difference_image(new_image, old_image, tolerance=0.05) +difference_image.show() +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/experiments/winCalNew.png b/experiments/winCalNew.png new file mode 100644 index 0000000000000000000000000000000000000000..804968440bbe7a6e376998b2990d39e0dba40e98 GIT binary patch literal 16821 zcmch<1yt1kpEqhF9RmW=IHZKMw7?)RbO}gFcXtRXHFS4(NH{bIqS76Tba$7O)cuUV z|K7dN-968JcK4jiIVdsl&G#!m1S=^>VPibTxOM9mwv04P<<_m+uv@q85I=kXezHu$ za0mQ%+et-A{8sq@`3Csro|%}u*sWX0NX$!v``~+Yduc7FTeompQJ=Rv?TU$`3-3gByMECj7X<+`h3f{dV!cZZMPkntAnvbE!%&5*blE55!=ekA;U#-n4 z;*R5hvhs_jW3iz-LChgpv}($D%8YuCqwa(}?9rFJjsKAN?(%ohIsIAvq?x{LTl&uv z6U!GK83t>$G#938>2o%wBPqN#o^s?76qs5pYc(R$DhqDVlH!q(5ofmgCLgKKB2XFu z2;3nzvv;AkIj=}Ykjc_DFwS^o{Sj@>&p4lTwBn;y`tHjE%fo<7;tqb;&cI=DDItno@lV31dWJ_VZrCq+(5+12CxMAB z9~1D>yX}1=eG`8}9?Qh?{GdCUcWX>Q6qzy!Ap^EI1mr?wBV-1qZrD^*ST+{uB%rOP zd*NoB(45EE{5TD4PlV-tHpd4`W%+Irn0xr!)nb)7p|mBeQAZe!I(Ck4US9nTqfp4k2yMFr8guVwr9muIbPy0o+g9-(^|KR9K?&(|om&#M1LClHsaTo$HY<$L3k{*iH{*7(owABrohsdnX2gf^UQf8R<0L7SYM zJaW&WTm$K{Y-_QV^fZ|9CXJa6-56WJVDBGgwmeiX_E;ZPX=ULWRk z-+z2bmO=4XGlG%GpP?N_FTvQNLWgh7?l{xv(jONct<0OLJ_d#Nnf)q$ zLg!G*sqelJP-!{JtW|9uUG5On7Hr8~Qu&Yt9vz+a=T8TjU7W@@Wr|J#H9sUdn+g%> z-38^4_M6bFeXrjes=u;LeD%Pt=4({w5&0;b?FpL=kk6<*m{M+p9o;%9mgBlDd2)TR zmzwl4OUj%v9qv0vYZ zwcUZ4hStH@%&ga{qPf6onkG8C`p?-?WGZi5FodD!cuZeEBknuR`~DOjSquz}Jg?K; zRpGN%@5_VEMyVAYw;7k!)24+$vcAEd-d?VjyII_!E>pEOvEb;Xc$D6SibB0Fyq2zP zrfSQg2%Q`qmoJY7`DCt#rKm=0)YOS}ouh-j^3X4oHsS8^{H((A3W8&bW-}#$;x) zkLh`pHI$%jdtJR4y{c=uvhfh7;ImDWiPA$Sjf#uLGV4oHzV=F4G(ucWRuE4pE92lW?Uih_kuG^CRzox@ zOW2aiXUE2n;htlQJ=)-8Hu1^8kFCA4NI6%Ii2UZJC;n-F4vWE>;5#P8(}z_^~e^BB0XfETY}$x(k)}O?ayN**QkREK@7xVJNh$o|`5? z;b8NpSh4xeWQ}Q8cohaIj=CK5t3hjEyvEa19;*WFr<0%ED$+bmH}5QxObC%4u$C0Zyk+q`Z#*s1kvAVTW!QbW61=N3)X|GM9`=d{NQXCRG?0P~JlWh`9 zdlFoDua1V2i-P|C{h)s5-{#qbSde zj_jpxC-syivM|x5^hXihXW7kC7O|_*ZNzWcBe4-gJkhzb3aZi44wplkeJHTd~ zA%h?V4-+#oWts10G=Ot2e$4nNq3m$6x1!4`^qOt0`cXrrYG9fRJdlu*zhtjS0Q$h0 z(R#94#`E{O0o)*Ih|CHtD`$;}t}Ri|vl}{ayfxN4kS^HI%O~wgGKOgLKM|2zXR6(8 zx$#l`-sVZT#2}xNr#&rv{8Lq>La!Ndk;^<&W2NErpl-)cI&eMu%hkzLzh@J%O{%6M zOx?_O_g&0%Wb3Q8t5uwh4N{dBF~7?D&UtbD{PUJ;Bd?tdvdP)bimi$8l?kAit2lIY z?T*yG`V-Z@ndGI15w0lg!-C;h5(<59!5*s z?k^3?OTr6A5JG72qYcHOv?_847%b$!^Pr?V3GDi|lyZ?w$p8yfuD#?kMRdmq)Mv=- znrN0fBgg2Xod$(9R{N46hcli2CZ(MxFloV!yx2a~0)m6AKgJdlmH9%K`>i9jHrf`m zO$}OgZhLwY4NiqkXN#enPOEZN0~SAvln=q+>+PiSEw%b^SN{XJ>{8DG#3YRwzYr%E zbS+KRZ*(rkBIE7r>%{!Bem1543&6I?{`|K_SnK&H)kX{P`#67h(Q_Hc6 zBGE2ezc%z}QD_CuVd|2>Xod+k>9d+s!-nxtC*KTiHdz`N%_Rm2&*V~Ep zS)NEmaNj2pG$7qVQd}7LiZUp(^RUr~?0>yW|LOky`>R+Vz8ZKQk81RjqS3XRWq7;%#EjK)X+ehQSepOOIXwzczy0>PPsr>l`ZB1-8^t0#j=HX4}){XsluTNAy zwQs4e?W%(VF>hw}MOc=a70f^BeR?ByKG?Q5s6YsZ#$hQ}N&Fy+kdVujii~)D*m@vj zGs&&m7$zkp8@^*g`RvV$pKo6Jo#qN3y0u*BdK5mG#|=8^0tn`LjlVhi(JbV!+4Gp0 zy=L+4S!+Xjf7HA3mg_%Ju8J4FSv!5$1Z1;*SNm10m*r=M4Rn&2!K8b4oq4sSzgC&{ z$FS)(#@)zWUZ0Wk&3RX8(TCK{U7yqyv<~s7J9Ln8jIJu(g*lB=UGJmmF6oEE5fA$! zCffW7V(FU_eJ~&SeguD#!fY^ub>_1hyDx6YsWi#ytXIVZ zUz)u2iP_}oi?a!=si=3ie8=D4MN2uQF3!%gf>=I}D>>jAUb33=(;&NBkuhf4pOTyx ze7bOx!EF&^i*Dm5`T402y$IiGdba>R4y90$6OjR#0w>INh%9_|tt4dYQ!2oPl+xYK zwM<`I0!4M2VWI1@rE;{r$r>y97>5j(Gzpm~@+ihun&iODVx{lmhOK+kU?S;~3BVrQ zjO9p%_6g0e<-*Vug%14XGK41jo@~%=ESLqKxCwFj)e#7$Ux}33L@!tE*cnEw*Gm_V^#ACl%vha1@ zUaD|JYh+{(;^GsTv)}2~*x=YQUvyyWH{WmfiIWIs3glCrZAjz3N9kNv^H6J1qc2AK z-3v5+sF%+dHn3SnQ(kiwx0u{OIeRGe|@ zkdPSKd^Ymf=)jg{a9ykt$>7J@TqyWIo+>t6`1;Zjrv8(jV@i)08|Nid{PLU9By3SE znp}OksYz(&sSmU2h*->vAWV3o^t+-DOKuV}WX-nf4$EEm=^sV-X5Dko1smi^0>yZU z6^82Xv}1m`YmS42UcrvRcmQ7)f**!AZ7pRy`sPdG(D6`I~-^2 zSF$}|S@CsGXMHYqR(7J5;#~>%H-#)l@`4B2=W53EtbFVQv(*%9?nd;NxFY7S)-{Vu z>gn5L*&5nhhFn-;wSS%7Ro7v6J%d+omYaOET%e;bjnqKSOa%WZ)82ySE~j zG57W~eIWk)xWc#0DSoNcv#}l72)C?t3X(y(CJDHdSnP_0_u)3-?)hGyuepT#I>;mT znvNp2vbHqou?WhUx$75SRNf7?AtS_j=KOqo0JCDVe1{R4&Q?o-jo!_>Y&aE0%GD!l zcJQuNk9E$h2AJ+X5#O;a}1tN&5{pf@UWC>=td$6$B%Oucu+P zGnxa&h!TQPZ*#JjRChWWCdH*oKZ(u;olXrCsw?M~^p(A?;jEBmDcYUnWh8&YD?@#r zMKddo-XDS6<~0Pdo|?B`t(FR9L2ti|oOpkhQ5 z=agX$ZT^GrXgGZ4F->Gmq|eTp_r1n5tsrhW&q#mi)mvf18e^W4pR~5)zM0ckr>CJ5 zG!3LJ5K{Z>ptSJSg&rTu;~X;oLPA^pxeo2Jb(AC#X+Agfp|umv+&7^>-*FfzE@SXw zirnSw$=V`OujbyU5yds7x2gAa3DgQo8}~f@pjGG>t4_VrSergeHy+w8_$FhSKq1$l zP*oGRVsSU+-4l!o^HDSFx^BF^(6PBWtiG4x(_EL$kDWweaA8K$H{;6goG{3D0P|)7 z)k7H}m{+X}Z|1M?tRAHO&Y8hIKuLjq&F2T57d{BUGYFp-WiLz`dvR&@(#tq!wdM8? z1*4cpvg~OTRxm;uN@2q-7XpJ#_Ufpp7(PNEEUO;#YzHEWm{F8PExb`B;9p<=r7v(Z zTrPZN(uDsyooPx)Mo1(7>s$yf>bjjYK!87L%km!?m7qETK`0B@6tgfZtNb%yJh*NE zL&6AJon_it3Ibyur*mJm{qA1#{e4-rI((8uiLe*rc4WjtGj6vhj5` zQ@jm;SGh1%Ca`Mv07{gnU1vK|?_i{>Jv`g&MaRxQxc&L`P@JfHVBi-IQp_;zxr3oj zEG^h6I_#JQ@aPKJY@jUV;>jQ--L_FMD);%@x+Ma&Gz8IwKzgTc7XKK@mvvziaQ1hv z5z8??ZG$MTk2pyCIIZ?YcgHeprEr=LHUYk@`|)R$*VjbmFd2xtZrtvz(PhoR z;rV9U#o>nU;L4Hla}`x{8SW*YHC=DFdgE$qTRw2!34W3By3V%D$)whLBFE`KJ^7>J zP(%_VxcK8#*qVQa*>0}eycFOs{HLN_HO&DYq z=Rln5{;j#OSlquv;FH&S5G}QDDmSZYU)q~(Fd^P)zM*v7upRMqo1cVHDO(2g#k1uJxqg<5gVsZ(s`@)F-^?a~LL`lIBRzN2@Q}qn@E* z>sL4vxTC4pjdu$F{Xf&zz13o$B~~-Ldd>87cSe91Lv)Vyea8|hlg~479Pdki zSl?+MrFZ|NT6#)NF5+E3KoP_%S#d$3=Q64;;5H4IO-u~ddvMyt=Q;VI(5b1=k4kzz z=PN}r_!LhT6xaO#z^7vdT+Ni*izzx)?!$>_F&0kNm0!r9Znz31Pl+6I;B#zw(dss? z9p9AdetzY1c_5`*l2%g9e>fsc>lmQz-cYlnMS@`@If-k8)*RUDKJzJ?d9vCf*D1bJ z_7u@Y^x%b5BPLHwR(eyG5p-l+tb$A&3PD}y@0i�*Ni^!-&1qWhw<;mV5wPDwLh~ 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 literal 0 HcmV?d00001 diff --git a/experiments/winCalOld.png b/experiments/winCalOld.png new file mode 100644 index 0000000000000000000000000000000000000000..0a7cd00687f90ca2f2b0ff2fa73e38b5f334a527 GIT binary patch literal 21555 zcmd43byQVdyEYEGDX9%e3CISdOF%j|snV%5(nxoQ8^ld20s@i(0+P}lN;e2fgLH#* z!*A~AdERs0Grn`i_{OjQaJXQvJ=a`w&2?Ybecjhhn3{?L{+)Yw(9qEEpFEb;Ktn^9 zMMJwqj*SU^vqZss3;c)fs-Yl_Ry;txj)n$9dm;D@&|2+l@UO%Rlq&1%Jw3;ae0O&y#czC46fEm>w)2gP&UdvF8te0(#4_=? zWIyMaA3J4Uzuu|nKWWZV>-6LMegubc+>(=%dqGO`ymoyf@5X59u550G)(5}z=&Bt# zW~$0hvC31!UnbjMY2U$kg@zp@C4sFadddIu)Q9iqW#_7v?*&_a(@lgI|MRW}RG_~rK7xFpMG zb5(ujZ9)KZI5nL}`yZ*n8=0RQ@oHH|u6Zk;xNjF=-?;_8h@Om{k{5`%y)A-ZQ*F;X zt-O(O%YPrPB6)Rqnn|Y+8gFH&q&$~quGy$g4$g*RN44xIGHX=Q1b*_NA`fInGDi$% z@7vO5t(065$Gos!Q-6pZ6l4z((zx8nr_{Q9Gu1@W);bdauY9SM`~GcuX;D)iA$AZN zcF0qcn_9`wiwf*bq_RdbVnbGJ@t5|DKc~)&^2p`qVQ9BtCRDE&IJvT~tc>L4QbpV~ zY>~-6SH>@|dsw!ZrhaLn#*tcsc%67ZOJCO-t#LMEM9$#_M)Z&!;a+{O0gH-;J^t9_ z;M;FVC+%`s8 z7npCUwwpesjtyjEXJ20zF3-pIctiq`YLUP73D-2Ym|QJ^y*LwJvMEWWJ$y~eB9e_= zReiaeLOV@Nj#)J&Q(j*(=Q#Z)EG=MvOn_H9RSFG~kNs*0qttBd;EUPd?BR&iFin|3AzF#3T`Bzu6;T~5ZK{?k)pPH&+t#?ch&!A2_PbF%)1!@_d3iqg zdY3n8@2VKucg|MRbF1v9ew-g|+AR0PZ@79On{TdmANilVWoh(S77qsd9 z!1>6u4Lj?co&-Cap`|H7c}r=3DfnyhpVxGu(^X)J{c{}-ThpFt3@UP~b4Z@K@l{^H z^+kLRDdH0nG)Hoj*CLvCGYBQ7r4(0g=A^s^llb1$dG4w#cD~KE==~7Zd&p)o!L?K= zhHWa4c{&{z6gHl;#M5Xrbe3|f%OW@8-j$Ic7n1tRm04SU%OG9V=D0SFY5HuVmZ4xk zLzqa)R9f_XQTdCL?OeTUfJ>oj%&eMLNUy3=@3Ea(zf%`6S?yS|^?tXd`*>hZzrq?` zZrQJ4@v}((ner;F*Kc&j->sqeig{SZy~6IQ#exdoWJ2-6#*!r+M{@A}Pv_r^HU|d! z?6s3eCE(mz54y#*lw;}rQLiqjp*8Y#U^|-yC+PhR_~e9w}&&k+?|r8vDB-n zu}#TEZr&se3tLswNw}UaCm0V?T0P1I3pJ9~h?Qs5II{NpmCL!$AeYacr>kG}lZfs- z^(>IHc#!Jn?aml%|iw*1Z>kcYwX_~K(N^CZN6*;SqBD}Z8dEA+oeO&!x^10ASO8L5D zRS_6;NXfOK*IW%tE_Evk&saauI-DX!JU1SbD!l!XM;Vyiyvv;_5XH9s#ws;iQtTkk zaaM16sA^+}rFE0#pogXXlUB(4o- z;iJYCg>c3aQjhiNFgCX6AE_h8IjI+6dht3x+AVUeCo<>GSBl!#JE$D{>O`kXj3v+8 zxdT4s^eHC`Xlp*34H{0SP=WVWq%o7q(lGw|qWPw$r$=ilRdaF4){@xQ%J3-eY`>TL zq@M)iF6Q5>L3q!5LJP+hf6sWzGn0$_I77$6;%*kj_Ur*azxb7#NWcQ5-`ic(zxL*C ze0*9aZm;R#zUfV;zT4eOlW&MPcOfgQm(DNHmyYDqboSjwYYV7yU>mHwCHnjBUgycw z>eA2yp36H2AB8;3ewBPGp-UTSB1yezO>>*6oV%Z5aPsZVXpy27zeUr*C-U_I|J=30 zADV98nzgc?edrS`aQ|R%-i2Q>{8?YrH6ru9pjpbTb_)T=*=hHlzD=(Pa&DEx9hbvy zO?|7jgeN95`xi;P2RV1>+oYzy!0yb;O!x_jYA&Guo63o+4xV@+hqS8Rd_2R|6j!`6 zqVIhDCo5{SRuhZg;`+vtGk4}fRijrUlVGkyg+0wza~pn&Mf>o$7)5l?>;0GpADL)nep9id=}q{C?K>ym;)PC zx%CZUF5iHVjJ?mFM?$Hvru`oF{79uUU8wGmH&)VA+sz8ghg@hw%zGP63DE@&==$aT4K$BBa?wAEv?|n+Dvn&f0uL|GpW{Xlmk_xunDMgf6sl?3TzMyO z87?R|a5WzOsL-^mT4iyVu5sQwl9+XFI^QVJp17o>?Vh(?XLyuEFKeZ{zJE}WHmI>b zX<26`Xjc!SNeF}da~Zt8C^a=w2522jr$8rF#dheoej(41vBpBYTxJgm^O|*g3|lB| zKL$GVYZ!n%YfZDip}k8M4S{_FFdpqyAaM}vJ1GDysTNk`+{Nt4=no@`LEhj;C`^@o z9TX0wL#nTHjRHi3Dcyp$JzUSU17CYg6avWpFeIy&M{X;>(V- z1vvix01b{8%hiL44n^KY$2QHb!;J?s!Hz|P@F>->P=Nt8ATVUi2A3Au|EGgfnM^n~ zoiWA7$A2N#2e4?;JsW;cz>Y#WMM#~p$e{MUd?dwu5!w>wt49zc?`6{SgY+Bflahl$ z$#fKEbLv$fFC>y3=9*GHcBY^3Sq)^L%mqrSrD3om@%BEwxu5x~NM92K^1BuO7stap zGxh5eR_UL;4^}=0QHy$TG#(6yzLFux?92~`aT(O8;iHv=VRgNulWF)!*DMDa@2s|) zkZwNzSu#4lJ=<7M+zjyloq*jqw8SP?B{k1=b>QiPu|i#$lUd&q;x3p5{ElgJz?Jo6 zg^gCV!%Rga8OL*IGl}!HZn@=`hFB(L7t&ss$pvc`8#a9e-CT>r^{Lr2E&IuePYVmC zdeshwJ*%r}zFhV*b)=bVo!$F(y%Gc8J0x0cj<+T#JLv~?;pdV5US4c7;exU zARhnWgNB?O#$L1c%g!frx?XCA00x|c25evuo`XDW*PcR#5Vw&(!mcaOhy{QxX&T*1 z>C4_Ji~FIFL*?`=>FDEBby_UW6o6D8)b0&3ec&>@m&k(2g#@o`_RZzs%_G@&iBVZ~ z2}V9o%nPDYZvUdh5W%%Ts*FQrU6;T=ov!sLG+a}FMK(LrwH4qcuHoT!w5Zd4sg7qF z3ax?p$UyxZR)dyp{v584j2N{gjd8}#HkkZwzFg*B+$2j$$Qo?E+IBoVU3{B6UHN|3 zt%2!Bk$!bZ9{)@omO`HY`C(XzufuGEuAv!tPi+LX$5#-zTp8s9BJ52{EuOkfwGXN# zYaO=y?5C==wkxg}DSYy7u47 z&&^EwWm2nLCl24`m4RkGFs?1K6`1=y>2!ZKh?37Tvc?628L4Cn&=QCK@EaEGP)8Ki zdN*^ro1s^~fW=BZiGnJ%^E&Ui<>l$#QWdbx=$;;apbWe$S2>|8~}WtHx1pAw(0PP;$r+F z<#+HHQ`K~_Gdr8iUM#t{m=pCrKn@HZMwc{22qk%lR_o!VC()u9=R?MUz1#(O86S)Lyh;rg3wf^t%qN>S14DjQ~5bBj%l5{ zG}pLrP%v?u+osDv6up0u+h&NK)FD&U18F5A%pq9n7@iM4Pi=Y1W)f6(O5sEE`MQF@ z6^A#3F8@^tosn}p>4pQ>lbfr#8|8-3Wx>6pBi-7ivXo?UpA-IASWZMDuf3aj`1c=j z&&4c$F9~S77xRd`?xFm|RB|HxdM*Mc((5KVzGGm9rwOg3aG`lVPKOLocs+C6Akg5o zw@`SMQcNoJCNVVf{?I^ho1Ux}zI1xMD}kk8%mTpz1BZz867OL#dtk?=$3Xu*Ng`91 zitf#1&0H1jv9Up0kwM-%Ii$}?O7BAOZy#nM@46)Wo?+O!TPh90e@B~virW=g<}0MbFo z+ov2d#Y~$E>NS7wpI0SI6SsYLipn3mL*^eKCFy;YAXpM`F*TRZ_wu|WUI}YghJ!kA zc$c^TP(n7qG|chWr$#~|#B(8n*B^qU=%Bxs*#f#`n(=h1pEN%@PO?hdJEF%Dl;1P7 z#5Sd?fldOo;`f^%;x_rHd+jyvq;+KuZ$qk3!a@T0W%aBNyT{eh*ho{%?%lSWr$4t< zDyRXqB79W?GxD5q*9w@rhn-3L@hc!g$VcFp`#kZtt8Z`+6ze;(%YZapLXo)i%{VK+ zX>7q>vi^=@Mn6z2X-7iqJ~)01C>YEplLJM;NB{+^yHN86$1ofgxZh%wplL&IB!3%h z_^%W4P#mqOQ_UEhFoZ1ym63@+fJu zdXh&Ejvc_UYb)h5bZ~VvaCL|Xjy90Ey70lVgU})ySXCkz8az^p`BOMJuBAhR_u*R^ zV1Sw20<$7od!R)Gjs?MS?R;bbim(QOiQh^MVMCx{FNK0*!4zjwFk5UWSc;5TH92rR z4OK&&PTH2VW8zT+StYr)QllpDH4Y6j{39A(38=7(DjFiuccli*G3YZG{DTKxKE^HB zJ6~}7(|nzzVBt6z!M(MIla^9B)SxG`eefXu>kKN?8BQJAU=VsKn1sa&_Fl_&U}l-z z6}_50YO-h$1ak(Y6bLD$nVIx#&Gz^Z9KDh*jcbh*!hR}Tqg81>F$1B>|;O!V8ntPN_>n82brt$x}7$KL?I z$LuBf43tONM6e*=Q{UtVqhUi7!6x8Pjl;YJgT4Th#`e6QPXLZNz|wyeQ@;lW3<0#B zVcI)Sn5mN`dqr_wzcmP3>!@8W0j$Uy*U@4V45O zv2V;aiUKWTtTHh?{LW;9H=lBc%xv0%E=!D*!r}>0JX)S4~Mp;xbbt{0uL$- zO*PioXPt4Z>s?mMOuZ-jL0MT@h3499qwfe9&Hq&>?EE{H8Q2UkYz{!Vizu0L$&LMD zIwC1aZuoGSRbzlCmi+!Cm{1}EFrCwU0?07A1O-^PQ${D0KDN*vQ6Bz@uQl1RY5gAO z@!D|fvp8({;Z*WhEOh7}Ig^1D;ZdL}gzz43PbnDIdnoI3(RL;YIh*#Z;*xPVUDewF zNh2rw9U-`rySFySN>p`49}!hm+Ky>}M1gptO~JXi>HMX$I33c|?IG9(e-vReZf7VN z!KZ16vup`J(PMiutN;UkH!GM9Y*XV~fZ1n)=}e5c=~}4>+6xekxE+Dp+*Th4(?;Ud zY;z^6_C52OCAL&ITTa8c2ecT=O3Whn^$btuA8!mb^_F%7>R{%p_1%e`?-FmLe z^N-w-kq?PC7~7A?iA0v$HV7(feEI?pD7_@;*qO?Wuj;_tx)cvKWXY+s^Izh$6x4nL ze-!m{mw>J8-TpQLOnh9nWmj0bxzpoouU)KQ1{X}N9p{wv5%>iRsXdK8><2R2ps7;I z5%R_yUpL3mJoO5@30`%+MXYjX0`)dRd9a}*uxrG78~iUk>Mr+t%RhYFStcmbtL%B1 z%yk>09!U`T*Pg3s+z#!1L{tgLu-eV_6-mAcTMksVSscq3+SvqiGIMMIhV=`|DKBHa*ITx)O^KRmg68=7F3GH#hYVT2x5~g;m z;gw4_W5N(&z@K2R+Vp^PR#bMX+gz@zMfKnX_;ms~KIL^gPU|GzjHMAQ!-I%6JnBTi=mEAua+X zQ@m&D0VNXTBM|+v>j~q*ON{*Jymk5ICYi3y_oJ8Imbv|7NWUqELEdrlP zAd+CVlngXyOOfrb*PMe_i_4eE~ zK9T8og}MX?3O^xGI=vb3JZf-NE*zM2AEUhixSy7nw~)}Ho%>o~Eno@YoR#+i#Q@(- z_+Jgtw?%N(A=%j2tbx#MgANKLVH>}S6nC309G<4_`tSOVmRTqsd3$>cZ%>rJxdwD^ z2|p)Wd^)szP(svbHI`ZR2gn8XCkYIbK)OGaU>xv-gjq$Y*X>946SodZE2w0FIv^PbOvi% z&BgI{IP1l9CvBizdz3P7w4ZRO!lD{Rj9esxXf^gbEcyr|5J89`dm9BMI?B&0Uuq-X zm8sR?6m%Ykm%A$X*uN!{&HbJ3IqU7Vl4QkHp=-cO8!9#~gfK3*jM|i%hyN@#6lLS& zjEaPjY4}#aeC$aSUW34&sh%Z2TxQX`aQ{c994nNe@FzXnbIt0aS+5j#K;+#VUtjEw0Lfp>^SK%(2a+Qc zfROh0RV6VmBj0U)(wURpxjAjL%$GF96ai*~@_xn$hPl*h6Nc6LOx`lce zPlg1vYvyVF4~Z)6r*!cBR`uu=o^ZtJS378HKBa+!f|3Hx{Kn4$*Y1`zz4yo25m0YK z<};=jT488>dnSwg-%%wEBcJupA(d^Yz`8n+YIA*gW`jje*zif_%@3dj-8U&(k~_11 zd)YIu`*bqf(K1=kQAa%!e=}4?`n_{o-B;g}oj407fjw2x2x+d)x}VCt#riz;?># z;(s}+CWrtMJjSnAKjb&Qb;Pt)ju{ou%ccG*F;*3`n63A8T!N@i-e>K6xSj3aHnP$k z%UqDpIJfn^u;j@Em=<4W;8{Z8ljpuxJX;gxxpn6I`yZc(|5(N`gzDmX`4q}YsYd9+ zVi{OM+dT!6F@_a=QD?#C&R!SAoQ2;YgUa4%4Rhjv!3RO&*9wmoq3&EmnngWe%cvp{Dh8nX7uGqlE~KV4Q0&t-c>M{q{gf)1YA5O?~PgL7o4+e zmya-~7T){HB49tMwapjeyzyYv&~o+gPgyUQrhdg!%71wQ)H4q#qLWS5>c_3p{mIhx z_IK?rq`Hiry!)lx^{cZlN-C>FUl9u>(T2xWbb7!a*^`ve z3OGzZ=L}ps>|rm-3!EgQ`xu>NaICCCFYEA*3{gE7AbOo0nr!m*)X?c0#>Qnb-(F0D z-;M+`Zsrr;>B&y}A65?!yc<|_p+{$xRE`ZXr;nkzN(k%4QmW{X3aFR8Di>DjdgTNE z?5X}$a&M~-W8P;$$ESOXi&rmwt{(0`=}@OQdi>1NnutCEojXOORga8lbwZS^d8I!& zcgFlTuh7I-96B4-y5sd`=7VmF&hqg+%kYEen1ne?tqo%S9`aPRg;@!H$$p93>Yx8SU21 zgCK08VvrQ0-Ga)V{#A*8Kny;;|JSET==^Q)2@DnqJ+mLYL}*^?sb$OFx`c^0ha9wp zkrZ3E49u9dC`-I=QR(2K5(IUftKC4_MNowC+zK_G(k{1D4sSOt)6+sP0Qu5UJT?~L zeI;woaf=;4r&vOQud$_yol%Otb-jJV_<`I{D_{OJe^w%`b4g}QCVO3{p<-h7>+H8> zM>i+=c=tG?>&RzwQrQ4)Qq}mFK5VDQ1Cl8%1Cf-gw**~vx`n@~bPRO&KZhzleF~-V z`Q1PK!rQyH3{BM4>9^$on=msNLAaI?+cc+yu07)zGg-#@TACJ{zQ&sYHi(}dTk7hf z2yL@U^JeNW=3cM*atnTlNj0z@l`w$s!0TX#;lb-+gTXVx#RnOtvJ=V`G=j=fa_5rz zsdPYzG9#0j*@beM4fgteGcAT<^H4Y>^7aZ_R5sF0_RJ!Sdz~m>0NKjdvNs)#v+b)^a@ZoIZ3{MI_Yup(tA24aY46 ziy02M129f2Cmg!RQBM7`j1jXck`W9ChX&7ofFV^SuiUn(rW{(!`&_CL&(F-U;BmJh zqLlR&wn+SYkC1!%=7fOhu<4I-e>(J&>=m=_IA&xd@E zx1I!J5q=oP>AEw(dK-0VBoJL$8b@2_!f=9>HuvuLl7 zO~6GkG8k~o2MTB<3lBaDI;topaA}_f93;lGG?!F8g@O=o-e5DND{oa$%)0b)%1gAB*8@9*I;a+X zmx?Xz>%yT|U>&GeNSE+O85JJc{CJ>OYKpO!dQsYWKMUmA%VL>N6h!6*x^44N&J_FU z8lFwV1V^rv;^yn~6w$10b%L=$++M$1ebs~ORrMImVz#zJ8Sw9s(VaY4JR*7XEm#Qa z-*67Ew_^K9V!7*CGlJGRP=pogTCyI_=o%_tni4)^`flv+)6YLABA;!aofpz{Oqm!o-id6dZH2rRi8 z{z(5=$dh~nJ74n?Sg!9I{i6AECywlKx*Voun(FGPrr3xw5t(c?f1810-or~j7uQX{ z5{}!hFgX0ovXfc#lwJJj?syT759cp?RxAX|np-Z8g4Do_Mk7-xNZNeP*K?!DT>W+E zio7LKM>b(9)x9aZM=5^k{>uYLJ-i%&ysU!h61lo`(vQ}^K==Qg<>(aYwF}-l^(1IL z*_z181e(jnP>u7V0ioj?wbVHJgY}e#6%o%J1E1pwD-yEOkxDAfmBah<@fKWu>@)oP z>#lVw)aa)8Je@`vV{AJO2P%wS-SqV0D*>}MV-67}6!M#>)1bjT!{aok>N z!q*|a%TucZ%oJH%N@^VO)BLr_LZ5_AQ2i&}X1ilP1=T`GwXL+3F~Fz0k#0`C|X5YVjJj=uFO?RlXz_ ztnNY7tH=9-@&Rs%#Y7j^k3rt4^R+Y&JPFM2Z)0m4YhIBz?>3$oExa1EwX*$H*qux< z`OxB6B+;(haWSLnwQ`kDkfDI|PP)R*u(&SYfi1>r+#VJP&eI{m^ErwW5?+*~6Zh$Q zl2d#Y@$4vFonoDXVVJG>2$t2vVdC5Rm}JxDAc%i+3WoIvOG9N$K#-Kd5Tz6?r0e-7 z{E`mh{S)p&vDF8Nh)l78F6NSF!ff^iTm4^wT^7k}fTQzAm{Jz|KX3zJ2^Y3$%k)DF z0<=7RBJksLaQW7I|G);Y3cR9$s1uNGnQsBGa6T}{SUn8E1qi~WlyrLq01jFPi0@cL zg2cj#090i{SQb~h^UKOK8qE>0_$A*@w9dpVF$IPFz=y6be9=&lg5}OiMVZS7HS->U z1dWYvJc(z~;NOsv5*KCt`bn<*tUyQE<0qSWyJre;R*V2HtzGbKpzz>Ze`Ksa7Yf2x zEIE)Vdle%pSwQcQzRUM@^#}>#TW6L`D(M!lD8MbuNBXZ)BMn6y4D^1D1+FXp%2N*p zHnh(H?}%|g5nU;aZq9w0c)ZpZucljp7$CL9A{eu*xAhJx-_Mi*^d_}QS{bC$(o7~C zNU`n3nUy;C%}|5SYox~N>Tv4tbRlQ@?KsLUjp3l==`nz>fQ~RC85tRQxB>tU(NFr7FX(u1M#G8EEItKSf*eoK@Tm^GZQj(eMfkxgEnq4G1k-)GjH>`PMVST7 z;BAXmvnGo?Y>o|X4&Yguv7=V%V$CRrA3S%#aj&6SD(+M&u%*WDfT#MSX&N2Da(lxP zY|2u|Ka2r9YS%t%fP1%b!M1|7nEQ6}Cq1fVRKL|7#YIp&6a*Uy zZ9@kFQU7b713?Q<5IZx4;n%-ybfqMVX))bz!s7Ga<8(uDHCKXa>7?<{SaSNl!P}x_ z2Z+IqK>y=qc#RE4CIXw9o`}w4{m1M3zyODeZ#ICBqQkJ8kt{f-AbDC3V@}Hes8>+^ z=>`P-Y@3ms*M!~6cg|KkrtGVSQJLM#4oAtGe#ZbAV7}?X#VsN47Zfcys$>kzrkC~T z0!4g9{88p~zaQGr&bPFxv;n7|w4YzW$ ze|aNc>NjCZf*KxUOVC>OvXaj_D`R*l;Gqw)L zoG5ZPW@kDiYh**zgB1lIuY$y2R;j&chQ^1!*Pl1)s`ghAK zVRy0E*|G`Yk)P>VdpGALH<_TERnSvR_fkO2y=kkgcMT-nPyTQ=rlnHze>{~P_qIC` zzF#7+gMK#><}!M^*Ga4I+4-WgdPaCA=TXCl#9d_wRn59?Cy+Tc9V)2Toys_S!ZHV6 z`v*VdY=%M9=e`N!t=0pw)c8faKqosjF*g9kCx8iooqISM|Mpb(>s+q}vdxayum1oK zpGsJ$5)E6I-`PrsUn+>1-ehVKI2 z^_J0dBYhYweiiDnL00EP-PWW|W;|*A0pWYu|K=v(aBaAuhV7tUcWCyV zn9m3FQTi0&$dnRsmof}Gr4jM*3~!v3yoa*g@snICRuc>qVy00aHy?pulT zuA_16xsao+2l`a4=hI9cFDiK=T^K0oUIH&$nf;W&JyDNBcB-`f@voJR$*z;%8IxtE zTB35ls{%H&S8+lZcq((b(i6~b`^zEh>b<6fXS%&fLQ$aorC1rFOS4|zu`&KfYRm`~ zCl *To2ifH1SF1&Ffp0#4lmFuVDV%u`^UN(lgL{fh-fTGAiAPg9!g=QSF@w?n$> z$EZL4*;X=o2C_vd#;IFo9<~|xI@P5m2>qq{3EiazDBpg~dky~wNBHSMwSCv*43>5m96YLDr938rk7Iiz@$&Cw* z4j)?fC7$-j-wcHLYtY*00U=M%Y{^XYY<#plDm9kzBHbvq|0wEv#Nc|v6c%?@=KJ^W zDQ;f;gB-*?tgNh`0AN|Li&hNkuAyu{E@R7;$>vte&CceDo317U9jjBq_RH`4;rspl zMFG@|`$Sx;=X7&UlU4SL;Ne+MGao67j)BdYYHyxU)NFq$R%VM7XyGfmoiY-qWhy~c zyT15fU0~mw6Mwcx-s4*}?928K4f3n7fq;JX{_y`KT>Mp=Q zl9Opsti|*H1Z#QCFH2brVLwg5qlf$lOZmT+eu4rEnwnBla4?c8EQRrZ0U>hww!fhR za2?>=(=swDmgqT2Mo7OjWobtwQ?r9_2-gAz(&%%Pr;;jC#;yZr4=@~!VNlTO{WdU- z-qN3vM9U@61o>3fEg$?5-o1S9 zKkytX%+Q?H3=H4UcD!lt?LR=v*;`Yy0t{f_j>9Zgc@-K+d)=mMDheI@ekByDXJP)# z*H*lOF%th4^em^?H|*qmm0I5!l) zc`BDqrA!hvJnr3mgO}XgI+oPJRlbNwwKK6=>`2ZiO+4pS&VGRES$Wwn>0RhjRfy<{ zp`(2W?AQV=mUJzi+gBGSor2F=2Bs}q22$era;d5Tm0@t|M#MU@a3H&>J^_03Hp(e3 zxa(nk>qh6Xkj4_kVV20+~?u3-c`0{4fr{V$^=i6aLFQF43JR&vS8Rb#M<<)njN;f3hPf| zAkZ-VeF4bu|8yD-k*OM(9XzH_jQ~(>%73UfU|N9Of6q8Z(QRQsxBtuVtmP@hIeqX_ zkSNFzYp$q0Ua+KZ4QDO?_xl0rjPr}0tyn{O_|jNZ!dz7-=zCGDiR3K#ss~CRzg7mp zT7KZrea~;4NNGpbhDGc5P6Xgwdwctas7aN>%u`T;$p$jO>#(*OJE{qO+h378O&2>t zf)0H^fL=rKCs1*ssOId8tdd1Wka1=nZH|oqje}Rf6Zri`L9qh(VV@^HFZ)1a17G@< z!nFrmEfJ#NHGe#dRUH-lv9>==JQ=husET@QWo%DXYuea+IZu5(jOk#YFg0FEQTB%) zhzJ_f`$rP8WOd07bzy1##}gHqUEUy-7x@T!Q;MFK(id32PNMsPNF?Zse`fUMw+qC3 z&i~|f_h6pg6ej;OJ-!8`)7sFdJ&)s}7wy+@Dt79KkGwYKWEhumlL3FV<6IMKf0|^VM5XmG!slp%l!-X2%pUm7GOd#- z$g*J@qLrM(pQ1)2iesZ8fSD-(H?A^;IkfHYV{DjmPI6>SI7VW(0365)*E1h%%1C^_ z$sy0V?0fGe79qyJXfpdO#VA;VvPiM!3UJ*<1B@a14UH$Y&=09`S$=x-dBuTREg4B8Vf%+~ zQ-?oPax(`|VZ?YUj--w(xj(wLEZSo-@upg_qfyO`2}`9U%}%CWCRZ1gu++B)6AukNrn<_P z$CZtK4*XsKh7bW7q`R4eS=^!+)AzT#{$M8q2e;u72cP5JaVSk4{?>?!Xn#vlYj39G z0~JeP*;W^X>L-Gf?#dX7$V(mVIFg~ls`MaFTbT*C*xsAbxE3Wm1dnF<3LjnuU~L^->eTC#I^k4ta^ ze{FdPtmnB56qUNcq2lvTyYN7kiwePi%MW-jRcnAqVc@I?Apai%fQphJdn51Ht$ZXQ&O>7^HECGE+428R=yymH)l~b|y23 z%KzPI#Pn?$Kn@S4Px?d+F2@T{|3weSA|@P_u(|_P=}5;y1*GtCyX1Gh^8M2If>R4` zzptIQ7qp~iXJ=nD)7H16ZHNqi`t+%3>GQ}QM|~NKh;zm`of0GQBM(BU0fHpJ1sGNQ zI#>P;;6|dTn!jPQilSeud;;xn56t`6kf2NGSquRz8GU8#4@h@_8e(CUmn0%dUnUJdEp7Zt#0)RGuf3fr%EJ4SR?F9c+hKcQ$hDgTHbpnG=9 zJPQYi30|q`x&7-M`7tnHieQ`J<49CMp2a< z9jJ?BYyBsg82Fn^)X(FSX7@o(iyH%Pu1{C)j3@cxjVG16thg=eGza)YxOAQN@n2)` zcr!dIfbEwOMY}*LEj^!Ku|O5VQ(Cx0`BHCh_2~+`GPy3kL3gqok7RtSDXvo~2LzKv zD@!MLF0p`>s02Pi@WAJX#VG}cvwWelj$ii>w;UT%YEi`zZqIklt-UnnVAH-(^Zg$G z4u!}6l{;*vS9rXsCS#xC7X@s$hGkqbc{%A(`T)4}ySB0(N6HP~!~@3RRry zfL}D3ZPvt0&3Ey&GN~}PJq|pMM~NK?#E6I)_c24a-^Y@l8(?IJHGBb3?BND72zV1{ z>mw0DFGpEKygK!;GbKAVKF3~G++m(pYWM~bb!-50Gn!<}g3Wrbrn;>Sc+w%SDP%xV z1n`I7s7?Jr9lBeyNFXA%Ar4*(`{_HudIZq_<(~d0K2%Xo+<ojYCck?p+osF#(7A-+IIY48LkFgSWw}js&m%9Svv#Xha2+0yL&7foe!t zq*9Ph6~P8@kq@s8XBq0(`5e8h13r;k*)aAV9gpdcC>k;#yh{D@M|RlDY0v{>^9_M< z2tt4FJ*o^+K+np5570$Z@`XCo%wx*kMkG$KR=^x4E5bk-KjED>z`UBI>~Em6h0kXp zj5!XvGhP~8&sMgE(|27P$y=*KKsQY~I22KT3 ztMO!+1uiJX<0BH=nZ0_(0LNIkeF~`dJ8!ivohqCKKvqBjM_FTJ5m*{Rr+vKlz?P~< zcu)2JnQyfLeF;w?x_I6hQ^aTE0(q4M)^Ky3DyFG%o12e9&Y7gPuh-8313g$nc+r}I zex9`$h5K|yxQoiS#D`Fl`Ypto{}6{>#p8Zyz(1B}gx-(|U@ye6YK~Sb`8;WuerP}W zh@0uvN>l@E1H&Q5a<#@B5rBC!jiKEdR(1kZQx1eC7bhq8oW)?6-swgT*hp3@RXtniODI z$-SJXAYETf>h0-4!T!bxXk^mmsj(-Km52s;mU>|DezB5e)J?@{DNf|aMFrtG z)p>1fnvJ23YrOyaHLQo`jUYagQleC6G)-2M|3we(ovt5%-R3lc(p{UTbgEa*{-~xZ z>nCay#q|70hvdbIEQD<+-%ZQ+0}3Ec(O3*Y32T7lA`TiF$xqbZ1pvJ{!t>T4c(Ib~ zQ<&~mzt*B|Q~ffrH|r##=jN;_qMLMM2!&Z5xSR|!up=kW%J`EB2UadJ3hjeMsQ8kx zs>bBb?d*@v&p=mKi4t%IzcYQwC)lvvQJ4$PPx@#TYvv-J#f0=JZx794tSTL`!vw5? zm$Pd9F#)|SAG#9AP*9*_F_p2>bD;MkOFl~D{@@7KTW{9yoOkv||Df?sbqt)NB>OpxIunpx^mD+ z&r8^jMAqR;G$eJ9=OD{3#cmlB{^&p*JCl;xxYmw(bQ&h%;X41CyyNZUA7|qGOP33` z^zSd1$J3<8*Ms(%QT=$KiNhSKY71aMpMY|8v6kzOxF+=ef~Sh!+Ghu;D9tbMo}(gU zTH(XI|EFL+K?fQ*k^4Ykwb43F=CJ#L6UfD=S^vrJ|E@axPnj6MtngmT zl~o1Ip~sm{`mmb-u||>+CJY%4AqG*55jg7p%BXl{gx?U*Xq2R`YH4UJoJDxLsL9!K zqkEJUKUoR2O6nKV(b4JX%&!_pm2TqW;@V7YW<4}f;IAeMvwG9#nQ|M8AkUNDlxi>>Hj(l{hTF_6{hm0{4KYu%(sD;baiY^!f{$iQuPFc?O{{6i(_+rSJ} zi;BS07Uhnpu`}<<6olvJ?^cZoGu8M+ z&7Phl?Pr;%jA~k1*}pDhJK0tNHKL~6FQuAQgZC%}?M_~t?7np@4oa=<3`!Md2S#NF zkZLOYeviRCr}i8gD=1xlhn|*do&_uBg~ywpZ*Mtgcj0W6@r?-or9@cq3Iz?ch78m3Rre27_IEe_F`}3^^ zx-aGMBO3=Y+(HiiL`9KH6tyrfouH~&aoq-+EyW}g5--A$iO;gnX&HzkB8-V=Do|Nn zLRll|gfOsID6&5*)`E;v64CXf!1Z|~qXRGr?{Af4pcMjHz1nh*!vd8fx|FY9yam-( z6O+WdpK|#?dZV{~AO&qa*c|;rB7S|k7=q0sDvE9DA^&PA2zK6SebKVw$XUU&M+_-x z-VYt!ENM|if9+Rq+{nRJBw=TVfvR7WuK+H>U;gREGNn=bf#sefiY;H%L^Q5$e z48<+QbSFgebn%!0A3zBgfK2H+X9!Ui7Hv&a2MxBNx+}tuo?&O^(%l=x`x|8!Gl1z8 zcz@z4Cik_%skEpjK5zQXB7s#onN+kNAcM?Z1WPMpd6~1?CbNc}Fw^_!Li;VDdj&vRtj zZ+U%r(S{`2fL0xh<2N&+gto2=eA$!Sej-zX(3DeR3`V5s9XhH>fjk)phm7{%R6J&& zGXAF^Ssc;_L`q;207ioUP!SOwRWhK90BWb2CV(xbjzhQERY+ZYN@CI z_je8i>Z#GVME@nZ0VlA2)}xn>8wLZSJP0sg0sHSy1BKdf8VC=|d&-_yupT;8s6`cz z|HfjJ6bq7+PGBGVSMEeeBa+=To}&5WN!TX&ZepA!DM+LW$6{+I4oPp7ku7%scLA@2 z&Jk%fA^4*|AhF=NmN~Px&9hT)4w(eaGpdc06z|GCiRYY8Q-i4){|5*TlpFw>KCxb^ zHWc*R6w}j44eW5@C1$k(CwhLLNkVGU0Qz5I^GprD^$;4m5?Km0jmvM)Z0{V>MR|6i zqYJhDnU68Vm2t-9e|hShpO^Qzb}`7%au6b#1hxDhb8wZvbMWL?T~Od#{LsSH2msCQ zQIgXlT4>qJ+D)@Bra1skx?3G%cL%kf ze=Y31AUny*2iyy{$Ghd92sq?c?!u!=?#bhe7_-((z0#w({DVJvaX#Ya)T&?1`G~j; z^j)#>EE#H2H$PeKil}G^=t^e0#5Ws&cqA#1jd#Z}lT^Fj_fCG98jw@?o6XdHtC#ce z%)e1nFB$0Zyh~<+4WAHkNoaGGzz0q{XMis13M2#+Q|X?*=hlQ6xOWj(_?6AF(tbmA z?N9D5Q-#QXrs*eS|4o`Mx|wj02k%Tn@gnWBbmN?6Ne_j5R6kprX=&nSESuM5Uvk3l zJ-&ayc%XZG8e_`*wU&CpTSo^BI9~bgo(q<+;a?S*V3>~?=%(uEb#=gdIoP#;`OiyW z#tt&+)EGw0N+PB)rT=_0YAN+i)u^(bc4PmCFqM*A3Qpr;1ME^80crsR!~hya8GuAZ z#H5t;e{^!~;ZWvbR5do2luMXmr`fTKN!CueG=|C)BU_Ve5uwN>F%fEtQKn@^WhvKX z*pxNIXK@O6HJ~X8Eo|ZfRo?$Kfry0`c*FWs9`rxz-~C&wPI#SX zoB7|8QOHtPhIthDBsNKXjcXh+0}bCJ2K+j5pk#e|511_2!%faaOA)YfU}xOOPGjG~ zgQ{QzS3W%r{){$o57wzMT%-9-mF|lIQwkt{p!tRI#1pVTao6WfN2>1vs++I4wl2Gz?VLKbA(t6i4=izkJmn~=6VY< z^gUbT!KIO$ZgnlmaPu_O(A}V1Q;~@Q3~rm9NnRc3>d`1QAD}h;ps^MSd`nDmJSYE~ zsxp8#I)#iBB3o&r#~AZUDgWemJ3K&)RjMci1F8S{ndpvb)o`i zBWSR3wP3A~)@6V*pl|C>_nX7C)CR}X*sbpubXhYI1}Ri^5zW@2-^5@h*}C5?TouKZ zRYY5mY?E;-kN^q)G4_Hv!XJ<=EyA%!9E z#={T&AyWR((2>#m8q2XhFRGs&H*={7;LN;HLfPy~IbYOKfdz-)j~P#*Z$`_hsSF<( zEny|Q1RWM_N0z{drguCZVl{VE9ddeK9e@+gu z6f}BNh)}L1+}C9AGCQ9^W6xTF5GdZUg~<)i11LeoWd@T1e^1q5wFYgy;YhSaT9AX+ zgPe{ld-zd4nx~=%_j2i!j7+OQQdYv0C6$+M>(*=$TIGeU!k}!(v(9OjvN+k(P?zMt zI8sEno(Ul{Lg_FX3Vhp52{=D+`i<4#x2zhI2BW00hS~X1^eq5@E<$l6H`5tL8Oldf zcbikOp5HrfEQ?j7>jpxRIp>`UzDlO52YTfOBd@vqHJGy-HV3z=IGH6F5i63)WpH-C zH770|Ak_;;^&*yKO(CL<_%gUnsAgF5`pUx5Y{2vBwJBFhvNLUU@f@6W$lq9%J zS?tcIijmqI+0g%f0Y`C8UKiU(eo=7frTo5~$bH3nKup8NyoIsye`>@KW$4yK-h7~CG(Vy<1zBmxSj zuTtGNhQ2|067Pod#2L%>^$pnwWAt4Q(V4uNs-bkUHvHZ0ojdV(HvbI4_#Lf1cRc2C z?n*rERcanW?}+c!p5QW+)=rBX{aa~oLyJ4Mfy%Dq-Q^kZUQm0Ztw}FV`8Yqne7{M; zX8$nK&~)3C+rATtTOh%Z!lS2H)nDdJ&xYx(lRLKRh>-semfd{crO)jDGbsAkIMLT7 zLox2eVy{=BT^cmIf?h#_vWt0sYvXgmSs?pv0M>iykii>;tq{KZXDAL9K5bW1fV3yOK+nsojYc`?d zsj6-Lkh!Cdx_0^fTKB!9ezqqo*Is1ybZbP{D>b&=dE0w$-Lal+I!(5n2i;V(74>0- zPXeVVoz5LjuM_WKPVbMHxfIzl!)i7jvNSJXSVD*+$_v@Lu>M9$To;Vve_X`$4Hiz4 z%*K*Rv@yT0O7-R2aJCm@GA)a&DmQGGUhi;Mf)aK0@sO1kRe@6;rmoDVX3O9l9`bN} zkC#z4bKYwQ%NG5!MXTkS;(aHMhWQx4YKTz!UBG-1L{Qe08YE^ec;wXvVER7#5V~`% zK*-3->gll!5nE_|pt;(a1a>*tFV~jj7CT*y>BF;)WraIHkBCvyHZG0Md%K*F7xCdl m-f}(7_WcJpZQQP?^p6n|rZtU@3J>BX;B(lVdWc7MkNgYTVr8)a literal 0 HcmV?d00001 From dda26991d813ab6689a55f21953ed3105c9fbd26 Mon Sep 17 00:00:00 2001 From: Animesh404 Date: Thu, 20 Jun 2024 18:27:23 +0530 Subject: [PATCH 2/9] feat:add combine segmentation in visual.py and extract difference in vision.py --- experiments/winCalNew.png | Bin 16821 -> 16522 bytes experiments/winCalOld.png | Bin 21555 -> 16206 bytes openadapt/strategies/visual.py | 50 +++++++++++++++++++++++++++++++++ openadapt/vision.py | 28 ++++++++++++++++++ 4 files changed, 78 insertions(+) diff --git a/experiments/winCalNew.png b/experiments/winCalNew.png index 804968440bbe7a6e376998b2990d39e0dba40e98..90d66f7712968417ac1c7c3f7d98a8224fdf0c5e 100644 GIT binary patch literal 16522 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 literal 16821 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�*Ni^!-&1qWhw<;mV5wPDwLh~ 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 diff --git a/experiments/winCalOld.png b/experiments/winCalOld.png index 0a7cd00687f90ca2f2b0ff2fa73e38b5f334a527..fb772536fa15adf6a77a4f201ec170ca020450b7 100644 GIT binary patch literal 16206 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%0ATNfIPT_qaCg#4g1@h9rSGU&Zp-O#qgxmDY zA-wt8NHW#!^K9|*Uyp&ZbTw`msNjn%6^~)+f1ug~YBy5GAj6A|r@G4?C3ZSus12M3 zVRH%z@rRX^|9mpiTkf0h)jgw_MQUz$j|{ZSAwTcC9I3Ib#l_Y{AnB;hOiJ3EE=--J zpBruf)9$b~rp?RG-w+HZvs)wBA~*e?PFeRCJNUiNoSL+eYTFWc$Yzc*vZQ5sZ-{1r z^H}9~>-*PzkTG#+I@Zzu_U5yw(@Xt2r|jkB<#oF-%vf$rAmO+sCMK%Dfv%>IMx-%; zG-;2!2QgcLCNG{uS^{RzmkE;@W#9hh1SgC>nciL?$flOMq7k*kNKOg&UI?NXeNZnm zbPQ0i`FxdzO4un&Ddn+(L(8>066h{m@{m9@7aMBS(!tHap@>A_+Rb3sMgpcfP_mi; z$66*qIjeDLLdD-Uz z#1NUk)l?v3TNU8X2S_<>f8gFpW&PaO*SCf+5Y}>IVC+&Ac4a%WCi;3fH***5lpywd zHD>w{og4)|)B5ALe~iciLCgxwJ*LnyYH-lga9f81R}hy@R0|28;Pe;5izEPST;dsK zC94k(M4~<*YAzPr4&;LY!?bvdUDSpM4tXjK0)VjxF+2CD0)Zh^ypB&pDf9z}2BGc- z&Z86}LfO;uM0{2o4+r9QrfRpi!sV>2zL?w(hdClR6Q|b-E=`UiiJ@VqJ%aBEKZ$z;zqC6cAy7+N$mT>La3*D8FemftYI=@e-E>$d~tg1s?o$s99 z*Z%%|h&9ufDl`cFR{agt`8z`a6&V>>_a`Qqql_2A!_)->b?FA02x0w20yf1f#guMh zHl^4sw6C7WwoUS*5fopuO%!uXry=jLh|)OLPw-=12SmCKN2!@x97oTMZbF!xSWqVV6V zmyx-rATLlfGc)U`Pl`{8eO1M>ErS{8rbLr*s`bIxSve~PHJXD)cs-s!8|acsxr zK>?!a!p_o;mF|@A$iMeTt`+S-c@`V@uXD$H^547 zowU=0UB4K&aCy07dSoDCVcKLqr`U9HDVdnFI^p@Y9`MTJVHrJ-KzF2y1(vGz$A2+O z6Q~&6yovU<`AYG!+ilk#O`Gp{c=QL~7 z6CP5FmlEDFgM%k!9Ev|Ks&jY%B?1 z#^TJ4gMU`0fpgKn5dS*1op-y10kvk%u?U(lQ;MYJF~q<1u6R8A?K!nyqteKWUbdAH z6|tS_1IAG{n?#FirEpOb)&~XYTxD@&|2+l@UO%Rlq&1%Jw3;ae0O&y#czC46fEm>w)2gP&UdvF8te0(#4_=? zWIyMaA3J4Uzuu|nKWWZV>-6LMegubc+>(=%dqGO`ymoyf@5X59u550G)(5}z=&Bt# zW~$0hvC31!UnbjMY2U$kg@zp@C4sFadddIu)Q9iqW#_7v?*&_a(@lgI|MRW}RG_~rK7xFpMG zb5(ujZ9)KZI5nL}`yZ*n8=0RQ@oHH|u6Zk;xNjF=-?;_8h@Om{k{5`%y)A-ZQ*F;X zt-O(O%YPrPB6)Rqnn|Y+8gFH&q&$~quGy$g4$g*RN44xIGHX=Q1b*_NA`fInGDi$% z@7vO5t(065$Gos!Q-6pZ6l4z((zx8nr_{Q9Gu1@W);bdauY9SM`~GcuX;D)iA$AZN zcF0qcn_9`wiwf*bq_RdbVnbGJ@t5|DKc~)&^2p`qVQ9BtCRDE&IJvT~tc>L4QbpV~ zY>~-6SH>@|dsw!ZrhaLn#*tcsc%67ZOJCO-t#LMEM9$#_M)Z&!;a+{O0gH-;J^t9_ z;M;FVC+%`s8 z7npCUwwpesjtyjEXJ20zF3-pIctiq`YLUP73D-2Ym|QJ^y*LwJvMEWWJ$y~eB9e_= zReiaeLOV@Nj#)J&Q(j*(=Q#Z)EG=MvOn_H9RSFG~kNs*0qttBd;EUPd?BR&iFin|3AzF#3T`Bzu6;T~5ZK{?k)pPH&+t#?ch&!A2_PbF%)1!@_d3iqg zdY3n8@2VKucg|MRbF1v9ew-g|+AR0PZ@79On{TdmANilVWoh(S77qsd9 z!1>6u4Lj?co&-Cap`|H7c}r=3DfnyhpVxGu(^X)J{c{}-ThpFt3@UP~b4Z@K@l{^H z^+kLRDdH0nG)Hoj*CLvCGYBQ7r4(0g=A^s^llb1$dG4w#cD~KE==~7Zd&p)o!L?K= zhHWa4c{&{z6gHl;#M5Xrbe3|f%OW@8-j$Ic7n1tRm04SU%OG9V=D0SFY5HuVmZ4xk zLzqa)R9f_XQTdCL?OeTUfJ>oj%&eMLNUy3=@3Ea(zf%`6S?yS|^?tXd`*>hZzrq?` zZrQJ4@v}((ner;F*Kc&j->sqeig{SZy~6IQ#exdoWJ2-6#*!r+M{@A}Pv_r^HU|d! z?6s3eCE(mz54y#*lw;}rQLiqjp*8Y#U^|-yC+PhR_~e9w}&&k+?|r8vDB-n zu}#TEZr&se3tLswNw}UaCm0V?T0P1I3pJ9~h?Qs5II{NpmCL!$AeYacr>kG}lZfs- z^(>IHc#!Jn?aml%|iw*1Z>kcYwX_~K(N^CZN6*;SqBD}Z8dEA+oeO&!x^10ASO8L5D zRS_6;NXfOK*IW%tE_Evk&saauI-DX!JU1SbD!l!XM;Vyiyvv;_5XH9s#ws;iQtTkk zaaM16sA^+}rFE0#pogXXlUB(4o- z;iJYCg>c3aQjhiNFgCX6AE_h8IjI+6dht3x+AVUeCo<>GSBl!#JE$D{>O`kXj3v+8 zxdT4s^eHC`Xlp*34H{0SP=WVWq%o7q(lGw|qWPw$r$=ilRdaF4){@xQ%J3-eY`>TL zq@M)iF6Q5>L3q!5LJP+hf6sWzGn0$_I77$6;%*kj_Ur*azxb7#NWcQ5-`ic(zxL*C ze0*9aZm;R#zUfV;zT4eOlW&MPcOfgQm(DNHmyYDqboSjwYYV7yU>mHwCHnjBUgycw z>eA2yp36H2AB8;3ewBPGp-UTSB1yezO>>*6oV%Z5aPsZVXpy27zeUr*C-U_I|J=30 zADV98nzgc?edrS`aQ|R%-i2Q>{8?YrH6ru9pjpbTb_)T=*=hHlzD=(Pa&DEx9hbvy zO?|7jgeN95`xi;P2RV1>+oYzy!0yb;O!x_jYA&Guo63o+4xV@+hqS8Rd_2R|6j!`6 zqVIhDCo5{SRuhZg;`+vtGk4}fRijrUlVGkyg+0wza~pn&Mf>o$7)5l?>;0GpADL)nep9id=}q{C?K>ym;)PC zx%CZUF5iHVjJ?mFM?$Hvru`oF{79uUU8wGmH&)VA+sz8ghg@hw%zGP63DE@&==$aT4K$BBa?wAEv?|n+Dvn&f0uL|GpW{Xlmk_xunDMgf6sl?3TzMyO z87?R|a5WzOsL-^mT4iyVu5sQwl9+XFI^QVJp17o>?Vh(?XLyuEFKeZ{zJE}WHmI>b zX<26`Xjc!SNeF}da~Zt8C^a=w2522jr$8rF#dheoej(41vBpBYTxJgm^O|*g3|lB| zKL$GVYZ!n%YfZDip}k8M4S{_FFdpqyAaM}vJ1GDysTNk`+{Nt4=no@`LEhj;C`^@o z9TX0wL#nTHjRHi3Dcyp$JzUSU17CYg6avWpFeIy&M{X;>(V- z1vvix01b{8%hiL44n^KY$2QHb!;J?s!Hz|P@F>->P=Nt8ATVUi2A3Au|EGgfnM^n~ zoiWA7$A2N#2e4?;JsW;cz>Y#WMM#~p$e{MUd?dwu5!w>wt49zc?`6{SgY+Bflahl$ z$#fKEbLv$fFC>y3=9*GHcBY^3Sq)^L%mqrSrD3om@%BEwxu5x~NM92K^1BuO7stap zGxh5eR_UL;4^}=0QHy$TG#(6yzLFux?92~`aT(O8;iHv=VRgNulWF)!*DMDa@2s|) zkZwNzSu#4lJ=<7M+zjyloq*jqw8SP?B{k1=b>QiPu|i#$lUd&q;x3p5{ElgJz?Jo6 zg^gCV!%Rga8OL*IGl}!HZn@=`hFB(L7t&ss$pvc`8#a9e-CT>r^{Lr2E&IuePYVmC zdeshwJ*%r}zFhV*b)=bVo!$F(y%Gc8J0x0cj<+T#JLv~?;pdV5US4c7;exU zARhnWgNB?O#$L1c%g!frx?XCA00x|c25evuo`XDW*PcR#5Vw&(!mcaOhy{QxX&T*1 z>C4_Ji~FIFL*?`=>FDEBby_UW6o6D8)b0&3ec&>@m&k(2g#@o`_RZzs%_G@&iBVZ~ z2}V9o%nPDYZvUdh5W%%Ts*FQrU6;T=ov!sLG+a}FMK(LrwH4qcuHoT!w5Zd4sg7qF z3ax?p$UyxZR)dyp{v584j2N{gjd8}#HkkZwzFg*B+$2j$$Qo?E+IBoVU3{B6UHN|3 zt%2!Bk$!bZ9{)@omO`HY`C(XzufuGEuAv!tPi+LX$5#-zTp8s9BJ52{EuOkfwGXN# zYaO=y?5C==wkxg}DSYy7u47 z&&^EwWm2nLCl24`m4RkGFs?1K6`1=y>2!ZKh?37Tvc?628L4Cn&=QCK@EaEGP)8Ki zdN*^ro1s^~fW=BZiGnJ%^E&Ui<>l$#QWdbx=$;;apbWe$S2>|8~}WtHx1pAw(0PP;$r+F z<#+HHQ`K~_Gdr8iUM#t{m=pCrKn@HZMwc{22qk%lR_o!VC()u9=R?MUz1#(O86S)Lyh;rg3wf^t%qN>S14DjQ~5bBj%l5{ zG}pLrP%v?u+osDv6up0u+h&NK)FD&U18F5A%pq9n7@iM4Pi=Y1W)f6(O5sEE`MQF@ z6^A#3F8@^tosn}p>4pQ>lbfr#8|8-3Wx>6pBi-7ivXo?UpA-IASWZMDuf3aj`1c=j z&&4c$F9~S77xRd`?xFm|RB|HxdM*Mc((5KVzGGm9rwOg3aG`lVPKOLocs+C6Akg5o zw@`SMQcNoJCNVVf{?I^ho1Ux}zI1xMD}kk8%mTpz1BZz867OL#dtk?=$3Xu*Ng`91 zitf#1&0H1jv9Up0kwM-%Ii$}?O7BAOZy#nM@46)Wo?+O!TPh90e@B~virW=g<}0MbFo z+ov2d#Y~$E>NS7wpI0SI6SsYLipn3mL*^eKCFy;YAXpM`F*TRZ_wu|WUI}YghJ!kA zc$c^TP(n7qG|chWr$#~|#B(8n*B^qU=%Bxs*#f#`n(=h1pEN%@PO?hdJEF%Dl;1P7 z#5Sd?fldOo;`f^%;x_rHd+jyvq;+KuZ$qk3!a@T0W%aBNyT{eh*ho{%?%lSWr$4t< zDyRXqB79W?GxD5q*9w@rhn-3L@hc!g$VcFp`#kZtt8Z`+6ze;(%YZapLXo)i%{VK+ zX>7q>vi^=@Mn6z2X-7iqJ~)01C>YEplLJM;NB{+^yHN86$1ofgxZh%wplL&IB!3%h z_^%W4P#mqOQ_UEhFoZ1ym63@+fJu zdXh&Ejvc_UYb)h5bZ~VvaCL|Xjy90Ey70lVgU})ySXCkz8az^p`BOMJuBAhR_u*R^ zV1Sw20<$7od!R)Gjs?MS?R;bbim(QOiQh^MVMCx{FNK0*!4zjwFk5UWSc;5TH92rR z4OK&&PTH2VW8zT+StYr)QllpDH4Y6j{39A(38=7(DjFiuccli*G3YZG{DTKxKE^HB zJ6~}7(|nzzVBt6z!M(MIla^9B)SxG`eefXu>kKN?8BQJAU=VsKn1sa&_Fl_&U}l-z z6}_50YO-h$1ak(Y6bLD$nVIx#&Gz^Z9KDh*jcbh*!hR}Tqg81>F$1B>|;O!V8ntPN_>n82brt$x}7$KL?I z$LuBf43tONM6e*=Q{UtVqhUi7!6x8Pjl;YJgT4Th#`e6QPXLZNz|wyeQ@;lW3<0#B zVcI)Sn5mN`dqr_wzcmP3>!@8W0j$Uy*U@4V45O zv2V;aiUKWTtTHh?{LW;9H=lBc%xv0%E=!D*!r}>0JX)S4~Mp;xbbt{0uL$- zO*PioXPt4Z>s?mMOuZ-jL0MT@h3499qwfe9&Hq&>?EE{H8Q2UkYz{!Vizu0L$&LMD zIwC1aZuoGSRbzlCmi+!Cm{1}EFrCwU0?07A1O-^PQ${D0KDN*vQ6Bz@uQl1RY5gAO z@!D|fvp8({;Z*WhEOh7}Ig^1D;ZdL}gzz43PbnDIdnoI3(RL;YIh*#Z;*xPVUDewF zNh2rw9U-`rySFySN>p`49}!hm+Ky>}M1gptO~JXi>HMX$I33c|?IG9(e-vReZf7VN z!KZ16vup`J(PMiutN;UkH!GM9Y*XV~fZ1n)=}e5c=~}4>+6xekxE+Dp+*Th4(?;Ud zY;z^6_C52OCAL&ITTa8c2ecT=O3Whn^$btuA8!mb^_F%7>R{%p_1%e`?-FmLe z^N-w-kq?PC7~7A?iA0v$HV7(feEI?pD7_@;*qO?Wuj;_tx)cvKWXY+s^Izh$6x4nL ze-!m{mw>J8-TpQLOnh9nWmj0bxzpoouU)KQ1{X}N9p{wv5%>iRsXdK8><2R2ps7;I z5%R_yUpL3mJoO5@30`%+MXYjX0`)dRd9a}*uxrG78~iUk>Mr+t%RhYFStcmbtL%B1 z%yk>09!U`T*Pg3s+z#!1L{tgLu-eV_6-mAcTMksVSscq3+SvqiGIMMIhV=`|DKBHa*ITx)O^KRmg68=7F3GH#hYVT2x5~g;m z;gw4_W5N(&z@K2R+Vp^PR#bMX+gz@zMfKnX_;ms~KIL^gPU|GzjHMAQ!-I%6JnBTi=mEAua+X zQ@m&D0VNXTBM|+v>j~q*ON{*Jymk5ICYi3y_oJ8Imbv|7NWUqELEdrlP zAd+CVlngXyOOfrb*PMe_i_4eE~ zK9T8og}MX?3O^xGI=vb3JZf-NE*zM2AEUhixSy7nw~)}Ho%>o~Eno@YoR#+i#Q@(- z_+Jgtw?%N(A=%j2tbx#MgANKLVH>}S6nC309G<4_`tSOVmRTqsd3$>cZ%>rJxdwD^ z2|p)Wd^)szP(svbHI`ZR2gn8XCkYIbK)OGaU>xv-gjq$Y*X>946SodZE2w0FIv^PbOvi% z&BgI{IP1l9CvBizdz3P7w4ZRO!lD{Rj9esxXf^gbEcyr|5J89`dm9BMI?B&0Uuq-X zm8sR?6m%Ykm%A$X*uN!{&HbJ3IqU7Vl4QkHp=-cO8!9#~gfK3*jM|i%hyN@#6lLS& zjEaPjY4}#aeC$aSUW34&sh%Z2TxQX`aQ{c994nNe@FzXnbIt0aS+5j#K;+#VUtjEw0Lfp>^SK%(2a+Qc zfROh0RV6VmBj0U)(wURpxjAjL%$GF96ai*~@_xn$hPl*h6Nc6LOx`lce zPlg1vYvyVF4~Z)6r*!cBR`uu=o^ZtJS378HKBa+!f|3Hx{Kn4$*Y1`zz4yo25m0YK z<};=jT488>dnSwg-%%wEBcJupA(d^Yz`8n+YIA*gW`jje*zif_%@3dj-8U&(k~_11 zd)YIu`*bqf(K1=kQAa%!e=}4?`n_{o-B;g}oj407fjw2x2x+d)x}VCt#riz;?># z;(s}+CWrtMJjSnAKjb&Qb;Pt)ju{ou%ccG*F;*3`n63A8T!N@i-e>K6xSj3aHnP$k z%UqDpIJfn^u;j@Em=<4W;8{Z8ljpuxJX;gxxpn6I`yZc(|5(N`gzDmX`4q}YsYd9+ zVi{OM+dT!6F@_a=QD?#C&R!SAoQ2;YgUa4%4Rhjv!3RO&*9wmoq3&EmnngWe%cvp{Dh8nX7uGqlE~KV4Q0&t-c>M{q{gf)1YA5O?~PgL7o4+e zmya-~7T){HB49tMwapjeyzyYv&~o+gPgyUQrhdg!%71wQ)H4q#qLWS5>c_3p{mIhx z_IK?rq`Hiry!)lx^{cZlN-C>FUl9u>(T2xWbb7!a*^`ve z3OGzZ=L}ps>|rm-3!EgQ`xu>NaICCCFYEA*3{gE7AbOo0nr!m*)X?c0#>Qnb-(F0D z-;M+`Zsrr;>B&y}A65?!yc<|_p+{$xRE`ZXr;nkzN(k%4QmW{X3aFR8Di>DjdgTNE z?5X}$a&M~-W8P;$$ESOXi&rmwt{(0`=}@OQdi>1NnutCEojXOORga8lbwZS^d8I!& zcgFlTuh7I-96B4-y5sd`=7VmF&hqg+%kYEen1ne?tqo%S9`aPRg;@!H$$p93>Yx8SU21 zgCK08VvrQ0-Ga)V{#A*8Kny;;|JSET==^Q)2@DnqJ+mLYL}*^?sb$OFx`c^0ha9wp zkrZ3E49u9dC`-I=QR(2K5(IUftKC4_MNowC+zK_G(k{1D4sSOt)6+sP0Qu5UJT?~L zeI;woaf=;4r&vOQud$_yol%Otb-jJV_<`I{D_{OJe^w%`b4g}QCVO3{p<-h7>+H8> zM>i+=c=tG?>&RzwQrQ4)Qq}mFK5VDQ1Cl8%1Cf-gw**~vx`n@~bPRO&KZhzleF~-V z`Q1PK!rQyH3{BM4>9^$on=msNLAaI?+cc+yu07)zGg-#@TACJ{zQ&sYHi(}dTk7hf z2yL@U^JeNW=3cM*atnTlNj0z@l`w$s!0TX#;lb-+gTXVx#RnOtvJ=V`G=j=fa_5rz zsdPYzG9#0j*@beM4fgteGcAT<^H4Y>^7aZ_R5sF0_RJ!Sdz~m>0NKjdvNs)#v+b)^a@ZoIZ3{MI_Yup(tA24aY46 ziy02M129f2Cmg!RQBM7`j1jXck`W9ChX&7ofFV^SuiUn(rW{(!`&_CL&(F-U;BmJh zqLlR&wn+SYkC1!%=7fOhu<4I-e>(J&>=m=_IA&xd@E zx1I!J5q=oP>AEw(dK-0VBoJL$8b@2_!f=9>HuvuLl7 zO~6GkG8k~o2MTB<3lBaDI;topaA}_f93;lGG?!F8g@O=o-e5DND{oa$%)0b)%1gAB*8@9*I;a+X zmx?Xz>%yT|U>&GeNSE+O85JJc{CJ>OYKpO!dQsYWKMUmA%VL>N6h!6*x^44N&J_FU z8lFwV1V^rv;^yn~6w$10b%L=$++M$1ebs~ORrMImVz#zJ8Sw9s(VaY4JR*7XEm#Qa z-*67Ew_^K9V!7*CGlJGRP=pogTCyI_=o%_tni4)^`flv+)6YLABA;!aofpz{Oqm!o-id6dZH2rRi8 z{z(5=$dh~nJ74n?Sg!9I{i6AECywlKx*Voun(FGPrr3xw5t(c?f1810-or~j7uQX{ z5{}!hFgX0ovXfc#lwJJj?syT759cp?RxAX|np-Z8g4Do_Mk7-xNZNeP*K?!DT>W+E zio7LKM>b(9)x9aZM=5^k{>uYLJ-i%&ysU!h61lo`(vQ}^K==Qg<>(aYwF}-l^(1IL z*_z181e(jnP>u7V0ioj?wbVHJgY}e#6%o%J1E1pwD-yEOkxDAfmBah<@fKWu>@)oP z>#lVw)aa)8Je@`vV{AJO2P%wS-SqV0D*>}MV-67}6!M#>)1bjT!{aok>N z!q*|a%TucZ%oJH%N@^VO)BLr_LZ5_AQ2i&}X1ilP1=T`GwXL+3F~Fz0k#0`C|X5YVjJj=uFO?RlXz_ ztnNY7tH=9-@&Rs%#Y7j^k3rt4^R+Y&JPFM2Z)0m4YhIBz?>3$oExa1EwX*$H*qux< z`OxB6B+;(haWSLnwQ`kDkfDI|PP)R*u(&SYfi1>r+#VJP&eI{m^ErwW5?+*~6Zh$Q zl2d#Y@$4vFonoDXVVJG>2$t2vVdC5Rm}JxDAc%i+3WoIvOG9N$K#-Kd5Tz6?r0e-7 z{E`mh{S)p&vDF8Nh)l78F6NSF!ff^iTm4^wT^7k}fTQzAm{Jz|KX3zJ2^Y3$%k)DF z0<=7RBJksLaQW7I|G);Y3cR9$s1uNGnQsBGa6T}{SUn8E1qi~WlyrLq01jFPi0@cL zg2cj#090i{SQb~h^UKOK8qE>0_$A*@w9dpVF$IPFz=y6be9=&lg5}OiMVZS7HS->U z1dWYvJc(z~;NOsv5*KCt`bn<*tUyQE<0qSWyJre;R*V2HtzGbKpzz>Ze`Ksa7Yf2x zEIE)Vdle%pSwQcQzRUM@^#}>#TW6L`D(M!lD8MbuNBXZ)BMn6y4D^1D1+FXp%2N*p zHnh(H?}%|g5nU;aZq9w0c)ZpZucljp7$CL9A{eu*xAhJx-_Mi*^d_}QS{bC$(o7~C zNU`n3nUy;C%}|5SYox~N>Tv4tbRlQ@?KsLUjp3l==`nz>fQ~RC85tRQxB>tU(NFr7FX(u1M#G8EEItKSf*eoK@Tm^GZQj(eMfkxgEnq4G1k-)GjH>`PMVST7 z;BAXmvnGo?Y>o|X4&Yguv7=V%V$CRrA3S%#aj&6SD(+M&u%*WDfT#MSX&N2Da(lxP zY|2u|Ka2r9YS%t%fP1%b!M1|7nEQ6}Cq1fVRKL|7#YIp&6a*Uy zZ9@kFQU7b713?Q<5IZx4;n%-ybfqMVX))bz!s7Ga<8(uDHCKXa>7?<{SaSNl!P}x_ z2Z+IqK>y=qc#RE4CIXw9o`}w4{m1M3zyODeZ#ICBqQkJ8kt{f-AbDC3V@}Hes8>+^ z=>`P-Y@3ms*M!~6cg|KkrtGVSQJLM#4oAtGe#ZbAV7}?X#VsN47Zfcys$>kzrkC~T z0!4g9{88p~zaQGr&bPFxv;n7|w4YzW$ ze|aNc>NjCZf*KxUOVC>OvXaj_D`R*l;Gqw)L zoG5ZPW@kDiYh**zgB1lIuY$y2R;j&chQ^1!*Pl1)s`ghAK zVRy0E*|G`Yk)P>VdpGALH<_TERnSvR_fkO2y=kkgcMT-nPyTQ=rlnHze>{~P_qIC` zzF#7+gMK#><}!M^*Ga4I+4-WgdPaCA=TXCl#9d_wRn59?Cy+Tc9V)2Toys_S!ZHV6 z`v*VdY=%M9=e`N!t=0pw)c8faKqosjF*g9kCx8iooqISM|Mpb(>s+q}vdxayum1oK zpGsJ$5)E6I-`PrsUn+>1-ehVKI2 z^_J0dBYhYweiiDnL00EP-PWW|W;|*A0pWYu|K=v(aBaAuhV7tUcWCyV zn9m3FQTi0&$dnRsmof}Gr4jM*3~!v3yoa*g@snICRuc>qVy00aHy?pulT zuA_16xsao+2l`a4=hI9cFDiK=T^K0oUIH&$nf;W&JyDNBcB-`f@voJR$*z;%8IxtE zTB35ls{%H&S8+lZcq((b(i6~b`^zEh>b<6fXS%&fLQ$aorC1rFOS4|zu`&KfYRm`~ zCl *To2ifH1SF1&Ffp0#4lmFuVDV%u`^UN(lgL{fh-fTGAiAPg9!g=QSF@w?n$> z$EZL4*;X=o2C_vd#;IFo9<~|xI@P5m2>qq{3EiazDBpg~dky~wNBHSMwSCv*43>5m96YLDr938rk7Iiz@$&Cw* z4j)?fC7$-j-wcHLYtY*00U=M%Y{^XYY<#plDm9kzBHbvq|0wEv#Nc|v6c%?@=KJ^W zDQ;f;gB-*?tgNh`0AN|Li&hNkuAyu{E@R7;$>vte&CceDo317U9jjBq_RH`4;rspl zMFG@|`$Sx;=X7&UlU4SL;Ne+MGao67j)BdYYHyxU)NFq$R%VM7XyGfmoiY-qWhy~c zyT15fU0~mw6Mwcx-s4*}?928K4f3n7fq;JX{_y`KT>Mp=Q zl9Opsti|*H1Z#QCFH2brVLwg5qlf$lOZmT+eu4rEnwnBla4?c8EQRrZ0U>hww!fhR za2?>=(=swDmgqT2Mo7OjWobtwQ?r9_2-gAz(&%%Pr;;jC#;yZr4=@~!VNlTO{WdU- z-qN3vM9U@61o>3fEg$?5-o1S9 zKkytX%+Q?H3=H4UcD!lt?LR=v*;`Yy0t{f_j>9Zgc@-K+d)=mMDheI@ekByDXJP)# z*H*lOF%th4^em^?H|*qmm0I5!l) zc`BDqrA!hvJnr3mgO}XgI+oPJRlbNwwKK6=>`2ZiO+4pS&VGRES$Wwn>0RhjRfy<{ zp`(2W?AQV=mUJzi+gBGSor2F=2Bs}q22$era;d5Tm0@t|M#MU@a3H&>J^_03Hp(e3 zxa(nk>qh6Xkj4_kVV20+~?u3-c`0{4fr{V$^=i6aLFQF43JR&vS8Rb#M<<)njN;f3hPf| zAkZ-VeF4bu|8yD-k*OM(9XzH_jQ~(>%73UfU|N9Of6q8Z(QRQsxBtuVtmP@hIeqX_ zkSNFzYp$q0Ua+KZ4QDO?_xl0rjPr}0tyn{O_|jNZ!dz7-=zCGDiR3K#ss~CRzg7mp zT7KZrea~;4NNGpbhDGc5P6Xgwdwctas7aN>%u`T;$p$jO>#(*OJE{qO+h378O&2>t zf)0H^fL=rKCs1*ssOId8tdd1Wka1=nZH|oqje}Rf6Zri`L9qh(VV@^HFZ)1a17G@< z!nFrmEfJ#NHGe#dRUH-lv9>==JQ=husET@QWo%DXYuea+IZu5(jOk#YFg0FEQTB%) zhzJ_f`$rP8WOd07bzy1##}gHqUEUy-7x@T!Q;MFK(id32PNMsPNF?Zse`fUMw+qC3 z&i~|f_h6pg6ej;OJ-!8`)7sFdJ&)s}7wy+@Dt79KkGwYKWEhumlL3FV<6IMKf0|^VM5XmG!slp%l!-X2%pUm7GOd#- z$g*J@qLrM(pQ1)2iesZ8fSD-(H?A^;IkfHYV{DjmPI6>SI7VW(0365)*E1h%%1C^_ z$sy0V?0fGe79qyJXfpdO#VA;VvPiM!3UJ*<1B@a14UH$Y&=09`S$=x-dBuTREg4B8Vf%+~ zQ-?oPax(`|VZ?YUj--w(xj(wLEZSo-@upg_qfyO`2}`9U%}%CWCRZ1gu++B)6AukNrn<_P z$CZtK4*XsKh7bW7q`R4eS=^!+)AzT#{$M8q2e;u72cP5JaVSk4{?>?!Xn#vlYj39G z0~JeP*;W^X>L-Gf?#dX7$V(mVIFg~ls`MaFTbT*C*xsAbxE3Wm1dnF<3LjnuU~L^->eTC#I^k4ta^ ze{FdPtmnB56qUNcq2lvTyYN7kiwePi%MW-jRcnAqVc@I?Apai%fQphJdn51Ht$ZXQ&O>7^HECGE+428R=yymH)l~b|y23 z%KzPI#Pn?$Kn@S4Px?d+F2@T{|3weSA|@P_u(|_P=}5;y1*GtCyX1Gh^8M2If>R4` zzptIQ7qp~iXJ=nD)7H16ZHNqi`t+%3>GQ}QM|~NKh;zm`of0GQBM(BU0fHpJ1sGNQ zI#>P;;6|dTn!jPQilSeud;;xn56t`6kf2NGSquRz8GU8#4@h@_8e(CUmn0%dUnUJdEp7Zt#0)RGuf3fr%EJ4SR?F9c+hKcQ$hDgTHbpnG=9 zJPQYi30|q`x&7-M`7tnHieQ`J<49CMp2a< z9jJ?BYyBsg82Fn^)X(FSX7@o(iyH%Pu1{C)j3@cxjVG16thg=eGza)YxOAQN@n2)` zcr!dIfbEwOMY}*LEj^!Ku|O5VQ(Cx0`BHCh_2~+`GPy3kL3gqok7RtSDXvo~2LzKv zD@!MLF0p`>s02Pi@WAJX#VG}cvwWelj$ii>w;UT%YEi`zZqIklt-UnnVAH-(^Zg$G z4u!}6l{;*vS9rXsCS#xC7X@s$hGkqbc{%A(`T)4}ySB0(N6HP~!~@3RRry zfL}D3ZPvt0&3Ey&GN~}PJq|pMM~NK?#E6I)_c24a-^Y@l8(?IJHGBb3?BND72zV1{ z>mw0DFGpEKygK!;GbKAVKF3~G++m(pYWM~bb!-50Gn!<}g3Wrbrn;>Sc+w%SDP%xV z1n`I7s7?Jr9lBeyNFXA%Ar4*(`{_HudIZq_<(~d0K2%Xo+<ojYCck?p+osF#(7A-+IIY48LkFgSWw}js&m%9Svv#Xha2+0yL&7foe!t zq*9Ph6~P8@kq@s8XBq0(`5e8h13r;k*)aAV9gpdcC>k;#yh{D@M|RlDY0v{>^9_M< z2tt4FJ*o^+K+np5570$Z@`XCo%wx*kMkG$KR=^x4E5bk-KjED>z`UBI>~Em6h0kXp zj5!XvGhP~8&sMgE(|27P$y=*KKsQY~I22KT3 ztMO!+1uiJX<0BH=nZ0_(0LNIkeF~`dJ8!ivohqCKKvqBjM_FTJ5m*{Rr+vKlz?P~< zcu)2JnQyfLeF;w?x_I6hQ^aTE0(q4M)^Ky3DyFG%o12e9&Y7gPuh-8313g$nc+r}I zex9`$h5K|yxQoiS#D`Fl`Ypto{}6{>#p8Zyz(1B}gx-(|U@ye6YK~Sb`8;WuerP}W zh@0uvN>l@E1H&Q5a<#@B5rBC!jiKEdR(1kZQx1eC7bhq8oW)?6-swgT*hp3@RXtniODI z$-SJXAYETf>h0-4!T!bxXk^mmsj(-Km52s;mU>|DezB5e)J?@{DNf|aMFrtG z)p>1fnvJ23YrOyaHLQo`jUYagQleC6G)-2M|3we(ovt5%-R3lc(p{UTbgEa*{-~xZ z>nCay#q|70hvdbIEQD<+-%ZQ+0}3Ec(O3*Y32T7lA`TiF$xqbZ1pvJ{!t>T4c(Ib~ zQ<&~mzt*B|Q~ffrH|r##=jN;_qMLMM2!&Z5xSR|!up=kW%J`EB2UadJ3hjeMsQ8kx zs>bBb?d*@v&p=mKi4t%IzcYQwC)lvvQJ4$PPx@#TYvv-J#f0=JZx794tSTL`!vw5? zm$Pd9F#)|SAG#9AP*9*_F_p2>bD;MkOFl~D{@@7KTW{9yoOkv||Df?sbqt)NB>OpxIunpx^mD+ z&r8^jMAqR;G$eJ9=OD{3#cmlB{^&p*JCl;xxYmw(bQ&h%;X41CyyNZUA7|qGOP33` z^zSd1$J3<8*Ms(%QT=$KiNhSKY71aMpMY|8v6kzOxF+=ef~Sh!+Ghu;D9tbMo}(gU zTH(XI|EFL+K?fQ*k^4Ykwb43F=CJ#L6UfD=S^vrJ|E@axPnj6MtngmT zl~o1Ip~sm{`mmb-u||>+CJY%4AqG*55jg7p%BXl{gx?U*Xq2R`YH4UJoJDxLsL9!K zqkEJUKUoR2O6nKV(b4JX%&!_pm2TqW;@V7YW<4}f;IAeMvwG9#nQ|M8AkUNDlxi>>Hj(l{hTF_6{hm0{4KYu%(sD;baiY^!f{$iQuPFc?O{{6i(_+rSJ} zi;BS07Uhnpu`}<<6olvJ?^cZoGu8M+ z&7Phl?Pr;%jA~k1*}pDhJK0tNHKL~6FQuAQgZC%}?M_~t?7np@4oa=<3`!Md2S#NF zkZLOYeviRCr}i8gD=1xlhn|*do&_uBg~ywpZ*Mtgcj0W6@r?-or9@cq3Iz?ch78m3Rre27_IEe_F`}3^^ zx-aGMBO3=Y+(HiiL`9KH6tyrfouH~&aoq-+EyW}g5--A$iO;gnX&HzkB8-V=Do|Nn zLRll|gfOsID6&5*)`E;v64CXf!1Z|~qXRGr?{Af4pcMjHz1nh*!vd8fx|FY9yam-( z6O+WdpK|#?dZV{~AO&qa*c|;rB7S|k7=q0sDvE9DA^&PA2zK6SebKVw$XUU&M+_-x z-VYt!ENM|if9+Rq+{nRJBw=TVfvR7WuK+H>U;gREGNn=bf#sefiY;H%L^Q5$e z48<+QbSFgebn%!0A3zBgfK2H+X9!Ui7Hv&a2MxBNx+}tuo?&O^(%l=x`x|8!Gl1z8 zcz@z4Cik_%skEpjK5zQXB7s#onN+kNAcM?Z1WPMpd6~1?CbNc}Fw^_!Li;VDdj&vRtj zZ+U%r(S{`2fL0xh<2N&+gto2=eA$!Sej-zX(3DeR3`V5s9XhH>fjk)phm7{%R6J&& zGXAF^Ssc;_L`q;207ioUP!SOwRWhK90BWb2CV(xbjzhQERY+ZYN@CI z_je8i>Z#GVME@nZ0VlA2)}xn>8wLZSJP0sg0sHSy1BKdf8VC=|d&-_yupT;8s6`cz z|HfjJ6bq7+PGBGVSMEeeBa+=To}&5WN!TX&ZepA!DM+LW$6{+I4oPp7ku7%scLA@2 z&Jk%fA^4*|AhF=NmN~Px&9hT)4w(eaGpdc06z|GCiRYY8Q-i4){|5*TlpFw>KCxb^ zHWc*R6w}j44eW5@C1$k(CwhLLNkVGU0Qz5I^GprD^$;4m5?Km0jmvM)Z0{V>MR|6i zqYJhDnU68Vm2t-9e|hShpO^Qzb}`7%au6b#1hxDhb8wZvbMWL?T~Od#{LsSH2msCQ zQIgXlT4>qJ+D)@Bra1skx?3G%cL%kf ze=Y31AUny*2iyy{$Ghd92sq?c?!u!=?#bhe7_-((z0#w({DVJvaX#Ya)T&?1`G~j; z^j)#>EE#H2H$PeKil}G^=t^e0#5Ws&cqA#1jd#Z}lT^Fj_fCG98jw@?o6XdHtC#ce z%)e1nFB$0Zyh~<+4WAHkNoaGGzz0q{XMis13M2#+Q|X?*=hlQ6xOWj(_?6AF(tbmA z?N9D5Q-#QXrs*eS|4o`Mx|wj02k%Tn@gnWBbmN?6Ne_j5R6kprX=&nSESuM5Uvk3l zJ-&ayc%XZG8e_`*wU&CpTSo^BI9~bgo(q<+;a?S*V3>~?=%(uEb#=gdIoP#;`OiyW z#tt&+)EGw0N+PB)rT=_0YAN+i)u^(bc4PmCFqM*A3Qpr;1ME^80crsR!~hya8GuAZ z#H5t;e{^!~;ZWvbR5do2luMXmr`fTKN!CueG=|C)BU_Ve5uwN>F%fEtQKn@^WhvKX z*pxNIXK@O6HJ~X8Eo|ZfRo?$Kfry0`c*FWs9`rxz-~C&wPI#SX zoB7|8QOHtPhIthDBsNKXjcXh+0}bCJ2K+j5pk#e|511_2!%faaOA)YfU}xOOPGjG~ zgQ{QzS3W%r{){$o57wzMT%-9-mF|lIQwkt{p!tRI#1pVTao6WfN2>1vs++I4wl2Gz?VLKbA(t6i4=izkJmn~=6VY< z^gUbT!KIO$ZgnlmaPu_O(A}V1Q;~@Q3~rm9NnRc3>d`1QAD}h;ps^MSd`nDmJSYE~ zsxp8#I)#iBB3o&r#~AZUDgWemJ3K&)RjMci1F8S{ndpvb)o`i zBWSR3wP3A~)@6V*pl|C>_nX7C)CR}X*sbpubXhYI1}Ri^5zW@2-^5@h*}C5?TouKZ zRYY5mY?E;-kN^q)G4_Hv!XJ<=EyA%!9E z#={T&AyWR((2>#m8q2XhFRGs&H*={7;LN;HLfPy~IbYOKfdz-)j~P#*Z$`_hsSF<( zEny|Q1RWM_N0z{drguCZVl{VE9ddeK9e@+gu z6f}BNh)}L1+}C9AGCQ9^W6xTF5GdZUg~<)i11LeoWd@T1e^1q5wFYgy;YhSaT9AX+ zgPe{ld-zd4nx~=%_j2i!j7+OQQdYv0C6$+M>(*=$TIGeU!k}!(v(9OjvN+k(P?zMt zI8sEno(Ul{Lg_FX3Vhp52{=D+`i<4#x2zhI2BW00hS~X1^eq5@E<$l6H`5tL8Oldf zcbikOp5HrfEQ?j7>jpxRIp>`UzDlO52YTfOBd@vqHJGy-HV3z=IGH6F5i63)WpH-C zH770|Ak_;;^&*yKO(CL<_%gUnsAgF5`pUx5Y{2vBwJBFhvNLUU@f@6W$lq9%J zS?tcIijmqI+0g%f0Y`C8UKiU(eo=7frTo5~$bH3nKup8NyoIsye`>@KW$4yK-h7~CG(Vy<1zBmxSj zuTtGNhQ2|067Pod#2L%>^$pnwWAt4Q(V4uNs-bkUHvHZ0ojdV(HvbI4_#Lf1cRc2C z?n*rERcanW?}+c!p5QW+)=rBX{aa~oLyJ4Mfy%Dq-Q^kZUQm0Ztw}FV`8Yqne7{M; zX8$nK&~)3C+rATtTOh%Z!lS2H)nDdJ&xYx(lRLKRh>-semfd{crO)jDGbsAkIMLT7 zLox2eVy{=BT^cmIf?h#_vWt0sYvXgmSs?pv0M>iykii>;tq{KZXDAL9K5bW1fV3yOK+nsojYc`?d zsj6-Lkh!Cdx_0^fTKB!9ezqqo*Is1ybZbP{D>b&=dE0w$-Lal+I!(5n2i;V(74>0- zPXeVVoz5LjuM_WKPVbMHxfIzl!)i7jvNSJXSVD*+$_v@Lu>M9$To;Vve_X`$4Hiz4 z%*K*Rv@yT0O7-R2aJCm@GA)a&DmQGGUhi;Mf)aK0@sO1kRe@6;rmoDVX3O9l9`bN} zkC#z4bKYwQ%NG5!MXTkS;(aHMhWQx4YKTz!UBG-1L{Qe08YE^ec;wXvVER7#5V~`% zK*-3->gll!5nE_|pt;(a1a>*tFV~jj7CT*y>BF;)WraIHkBCvyHZG0Md%K*F7xCdl m-f}(7_WcJpZQQP?^p6n|rZtU@3J>BX;B(lVdWc7MkNgYTVr8)a diff --git a/openadapt/strategies/visual.py b/openadapt/strategies/visual.py index a19aa93d2..b9815ff3c 100644 --- a/openadapt/strategies/visual.py +++ b/openadapt/strategies/visual.py @@ -47,9 +47,11 @@ from dataclasses import dataclass from pprint import pformat import time +from xml.etree.ElementPath import find from loguru import logger from PIL import Image, ImageDraw +from typing import List import numpy as np from openadapt import adapters, common, models, plotting, strategies, utils, vision @@ -317,6 +319,7 @@ def get_active_segment( return active_index + def find_similar_image_segmentation( image: Image.Image, min_ssim: float = MIN_SCREENSHOT_SSIM, @@ -353,6 +356,33 @@ def find_similar_image_segmentation( return similar_segmentation, similar_segmentation_diff + + +def combine_segmentations( + previous_segmentation: Segmentation, + new_descriptions: List[str], + new_masked_images: List[Image.Image], + new_masks: List[np.ndarray] +) -> Segmentation: + + combined_image = previous_segmentation.image + combined_masked_images = previous_segmentation.masked_images + new_masked_images + combined_descriptions = previous_segmentation.descriptions + new_descriptions + + previous_bounding_boxes, previous_centroids = vision.calculate_bounding_boxes(previous_segmentation.masks) + new_bounding_boxes, new_centroids = vision.calculate_bounding_boxes(new_masks) + + combined_bounding_boxes = previous_bounding_boxes + new_bounding_boxes + combined_centroids = previous_centroids + new_centroids + + return Segmentation( + image=combined_image, + masked_images=combined_masked_images, + descriptions=combined_descriptions, + bounding_boxes=combined_bounding_boxes, + centroids=combined_centroids + ) + def get_window_segmentation( action_event: models.ActionEvent, @@ -382,6 +412,26 @@ def get_window_segmentation( # TODO XXX: create copy of similar_segmentation, but overwrite with segments of # regions of new image where segments of similar_segmentation overlap non-zero # regions of similar_segmentation_diff + new_image = vision.extract_difference_image( + original_image, + similar_segmentation.image, + tolerance=0.05, + ) + new_masks = vision.get_masks_from_segmented_image(new_image) + new_masked_images = vision.extract_masked_images(new_image, new_masks) + new_descriptions = prompt_for_descriptions( + new_image, + new_masked_images, + action_event.active_segment_description, + exceptions, + ) + updated_segmentation = combine_segmentations( + similar_segmentation, + new_descriptions, + new_masked_images, + new_masks, + ) + similar_segmentation = updated_segmentation return similar_segmentation segmentation_adapter = adapters.get_default_segmentation_adapter() diff --git a/openadapt/vision.py b/openadapt/vision.py index 65c3660ec..0023c1125 100644 --- a/openadapt/vision.py +++ b/openadapt/vision.py @@ -127,6 +127,34 @@ def refine_masks(masks: list[np.ndarray]) -> list[np.ndarray]: logger.info(f"{len(refined_masks)=}") return refined_masks +@cache.cache() +def extract_difference_image( + new_image: Image.Image, + old_image: Image.Image, + tolerance: float = 0.05, +) -> Image.Image: + """Extract the portion of the new image that is different from the old image.""" + new_image_np = np.array(new_image.convert('L')) + old_image_np = np.array(old_image.convert('L')) + + # Compute the SSIM between the two images + score, diff = ssim(new_image_np, old_image_np, full=True) + diff = (diff * 255).astype("uint8") + + # Threshold the difference image to get the regions that are different + thresh = cv2.threshold(diff, 255 * (1 - tolerance), 255, cv2.THRESH_BINARY_INV)[1] + + # Find contours of the different regions + contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + + # Create a mask of the differences + mask = np.zeros_like(new_image_np) + cv2.drawContours(mask, contours, -1, (255), thickness=cv2.FILLED) + + # Apply the mask to the new image to extract the different regions + diff_image_np = cv2.bitwise_and(np.array(new_image), np.array(new_image), mask=mask) + + return Image.fromarray(diff_image_np) @cache.cache() def filter_thin_ragged_masks( From 7dada60a6f9f965e1119a3c55d342202254989f2 Mon Sep 17 00:00:00 2001 From: Animesh404 Date: Thu, 20 Jun 2024 19:53:12 +0530 Subject: [PATCH 3/9] fix: filter out the masked_image and the desc that are not relevant for the new image, removed unnecessary contours in extract_difference_image --- experiments/extract_difference_image.py | 45 ----------------- experiments/winCalNew.png | Bin 16522 -> 0 bytes experiments/winCalOld.png | Bin 16206 -> 0 bytes openadapt/strategies/visual.py | 62 ++++++++++++++++-------- openadapt/vision.py | 23 ++++----- 5 files changed, 52 insertions(+), 78 deletions(-) delete mode 100644 experiments/extract_difference_image.py delete mode 100644 experiments/winCalNew.png delete mode 100644 experiments/winCalOld.png diff --git a/experiments/extract_difference_image.py b/experiments/extract_difference_image.py deleted file mode 100644 index 8ba39ea3b..000000000 --- a/experiments/extract_difference_image.py +++ /dev/null @@ -1,45 +0,0 @@ -from PIL import Image -import numpy as np -import cv2 -from skimage.metrics import structural_similarity as ssim - -def extract_difference_image( - new_image: Image.Image, - old_image: Image.Image, - tolerance: float = 0.05, -) -> Image.Image: - """Extract the portion of the new image that is different from the old image.""" - new_image_np = np.array(new_image.convert('L')) - old_image_np = np.array(old_image.convert('L')) - - # Compute the SSIM between the two images - score, diff = ssim(new_image_np, old_image_np, full=True) - diff = (diff * 255).astype("uint8") - - # Threshold the difference image to get the regions that are different - thresh = cv2.threshold(diff, 255 * (1 - tolerance), 255, cv2.THRESH_BINARY_INV)[1] - - # Find contours of the different regions - contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) - - # Create a mask of the differences - mask = np.zeros_like(new_image_np) - cv2.drawContours(mask, contours, -1, (255), thickness=cv2.FILLED) - - # Apply the mask to the new image to extract the different regions - diff_image_np = cv2.bitwise_and(np.array(new_image), np.array(new_image), mask=mask) - - return Image.fromarray(diff_image_np) - -# Example usage: -# new_image = Image.open('path_to_new_image') -# old_image = Image.open('path_to_old_image') -# difference_image = extract_difference_image(new_image, old_image, tolerance=0.05) -# difference_image.show() - -new_image = Image.open('./winCalNew.png') -old_image = Image.open('./winCalOld.png') -difference_image = extract_difference_image(new_image, old_image, tolerance=0.05) -difference_image.show() -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/experiments/winCalNew.png b/experiments/winCalNew.png deleted file mode 100644 index 90d66f7712968417ac1c7c3f7d98a8224fdf0c5e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16522 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 diff --git a/experiments/winCalOld.png b/experiments/winCalOld.png deleted file mode 100644 index fb772536fa15adf6a77a4f201ec170ca020450b7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16206 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%0ATNfIPT_qaCg#4g1@h9rSGU&Zp-O#qgxmDY zA-wt8NHW#!^K9|*Uyp&ZbTw`msNjn%6^~)+f1ug~YBy5GAj6A|r@G4?C3ZSus12M3 zVRH%z@rRX^|9mpiTkf0h)jgw_MQUz$j|{ZSAwTcC9I3Ib#l_Y{AnB;hOiJ3EE=--J zpBruf)9$b~rp?RG-w+HZvs)wBA~*e?PFeRCJNUiNoSL+eYTFWc$Yzc*vZQ5sZ-{1r z^H}9~>-*PzkTG#+I@Zzu_U5yw(@Xt2r|jkB<#oF-%vf$rAmO+sCMK%Dfv%>IMx-%; zG-;2!2QgcLCNG{uS^{RzmkE;@W#9hh1SgC>nciL?$flOMq7k*kNKOg&UI?NXeNZnm zbPQ0i`FxdzO4un&Ddn+(L(8>066h{m@{m9@7aMBS(!tHap@>A_+Rb3sMgpcfP_mi; z$66*qIjeDLLdD-Uz z#1NUk)l?v3TNU8X2S_<>f8gFpW&PaO*SCf+5Y}>IVC+&Ac4a%WCi;3fH***5lpywd zHD>w{og4)|)B5ALe~iciLCgxwJ*LnyYH-lga9f81R}hy@R0|28;Pe;5izEPST;dsK zC94k(M4~<*YAzPr4&;LY!?bvdUDSpM4tXjK0)VjxF+2CD0)Zh^ypB&pDf9z}2BGc- z&Z86}LfO;uM0{2o4+r9QrfRpi!sV>2zL?w(hdClR6Q|b-E=`UiiJ@VqJ%aBEKZ$z;zqC6cAy7+N$mT>La3*D8FemftYI=@e-E>$d~tg1s?o$s99 z*Z%%|h&9ufDl`cFR{agt`8z`a6&V>>_a`Qqql_2A!_)->b?FA02x0w20yf1f#guMh zHl^4sw6C7WwoUS*5fopuO%!uXry=jLh|)OLPw-=12SmCKN2!@x97oTMZbF!xSWqVV6V zmyx-rATLlfGc)U`Pl`{8eO1M>ErS{8rbLr*s`bIxSve~PHJXD)cs-s!8|acsxr zK>?!a!p_o;mF|@A$iMeTt`+S-c@`V@uXD$H^547 zowU=0UB4K&aCy07dSoDCVcKLqr`U9HDVdnFI^p@Y9`MTJVHrJ-KzF2y1(vGz$A2+O z6Q~&6yovU<`AYG!+ilk#O`Gp{c=QL~7 z6CP5FmlEDFgM%k!9Ev|Ks&jY%B?1 z#^TJ4gMU`0fpgKn5dS*1op-y10kvk%u?U(lQ;MYJF~q<1u6R8A?K!nyqteKWUbdAH z6|tS_1IAG{n?#FirEpOb)&~XYTx Segmentation: + """Combine the previous segmentation with the new segmentation of the differences. - combined_image = previous_segmentation.image - combined_masked_images = previous_segmentation.masked_images + new_masked_images - combined_descriptions = previous_segmentation.descriptions + new_descriptions + Args: + new_image: The new image which includes the changes. + previous_segmentation: The previous segmentation containing unchanged segments. + new_descriptions: Descriptions of the new segments from the difference image. + new_masked_images: Masked images of the new segments from the difference image. + new_masks: masks of the new segments. + + Returns: + Segmentation: A new segmentation combining both previous and new segments. + """ + def masks_overlap(mask1, mask2): + """Check if two masks overlap.""" + return np.any(np.logical_and(mask1, mask2)) - previous_bounding_boxes, previous_centroids = vision.calculate_bounding_boxes(previous_segmentation.masks) + # Calculate the bounding boxes and centroids for the new segments new_bounding_boxes, new_centroids = vision.calculate_bounding_boxes(new_masks) - combined_bounding_boxes = previous_bounding_boxes + new_bounding_boxes - combined_centroids = previous_centroids + new_centroids + # Filter out overlapping previous segments + filtered_previous_masked_images = [] + filtered_previous_descriptions = [] + filtered_previous_bounding_boxes = [] + filtered_previous_centroids = [] + for idx, prev_mask in enumerate(previous_segmentation.masks): + if not any(masks_overlap(prev_mask, new_mask) for new_mask in new_masks): + filtered_previous_masked_images.append(previous_segmentation.masked_images[idx]) + filtered_previous_descriptions.append(previous_segmentation.descriptions[idx]) + filtered_previous_bounding_boxes.append(previous_segmentation.bounding_boxes[idx]) + filtered_previous_centroids.append(previous_segmentation.centroids[idx]) + + # Combine filtered previous segments with new segments + combined_masked_images = filtered_previous_masked_images + new_masked_images + combined_descriptions = filtered_previous_descriptions + new_descriptions + combined_bounding_boxes = filtered_previous_bounding_boxes + new_bounding_boxes + combined_centroids = filtered_previous_centroids + new_centroids return Segmentation( - image=combined_image, + image=new_image, masked_images=combined_masked_images, descriptions=combined_descriptions, bounding_boxes=combined_bounding_boxes, @@ -412,20 +433,21 @@ def get_window_segmentation( # TODO XXX: create copy of similar_segmentation, but overwrite with segments of # regions of new image where segments of similar_segmentation overlap non-zero # regions of similar_segmentation_diff - new_image = vision.extract_difference_image( + difference_image = vision.extract_difference_image( original_image, similar_segmentation.image, tolerance=0.05, ) - new_masks = vision.get_masks_from_segmented_image(new_image) - new_masked_images = vision.extract_masked_images(new_image, new_masks) + new_masks = vision.get_masks_from_segmented_image(difference_image) + new_masked_images = vision.extract_masked_images(difference_image, new_masks) new_descriptions = prompt_for_descriptions( - new_image, + difference_image, new_masked_images, action_event.active_segment_description, exceptions, ) updated_segmentation = combine_segmentations( + difference_image, similar_segmentation, new_descriptions, new_masked_images, diff --git a/openadapt/vision.py b/openadapt/vision.py index 0023c1125..3824203b0 100644 --- a/openadapt/vision.py +++ b/openadapt/vision.py @@ -137,23 +137,19 @@ def extract_difference_image( new_image_np = np.array(new_image.convert('L')) old_image_np = np.array(old_image.convert('L')) - # Compute the SSIM between the two images - score, diff = ssim(new_image_np, old_image_np, full=True) - diff = (diff * 255).astype("uint8") + # Compute the absolute difference between the two images + diff = np.abs(new_image_np - old_image_np) - # Threshold the difference image to get the regions that are different - thresh = cv2.threshold(diff, 255 * (1 - tolerance), 255, cv2.THRESH_BINARY_INV)[1] + # Create a mask for the regions where the difference is above the tolerance + mask = diff > (255 * tolerance) - # Find contours of the different regions - contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + # Initialize an array for the difference image + diff_image_np = np.zeros_like(new_image_np) - # Create a mask of the differences - mask = np.zeros_like(new_image_np) - cv2.drawContours(mask, contours, -1, (255), thickness=cv2.FILLED) - - # Apply the mask to the new image to extract the different regions - diff_image_np = cv2.bitwise_and(np.array(new_image), np.array(new_image), mask=mask) + # Set the pixels that are different in the new image + diff_image_np[mask] = new_image_np[mask] + # Convert the numpy array back to an image return Image.fromarray(diff_image_np) @cache.cache() @@ -322,6 +318,7 @@ def calculate_bounding_boxes( return bounding_boxes, centroids + def get_image_similarity( im1: Image.Image, im2: Image.Image, From 65c876f2dfe2063e70e180561c347e036c2da931 Mon Sep 17 00:00:00 2001 From: Animesh404 Date: Sat, 22 Jun 2024 02:33:19 +0530 Subject: [PATCH 4/9] WIP --- openadapt/strategies/visual.py | 8 ++++++++ openadapt/vision.py | 27 +-------------------------- 2 files changed, 9 insertions(+), 26 deletions(-) diff --git a/openadapt/strategies/visual.py b/openadapt/strategies/visual.py index c6d5dc9ba..07e27c60a 100644 --- a/openadapt/strategies/visual.py +++ b/openadapt/strategies/visual.py @@ -47,6 +47,7 @@ from dataclasses import dataclass from pprint import pformat import time +from turtle import title from loguru import logger from PIL import Image, ImageDraw import numpy as np @@ -585,3 +586,10 @@ def prompt_for_descriptions( # remove indexes descriptions = [desc for idx, desc in descriptions] return descriptions + +#Example usage for visualizing +image_1 = Image.open("./winCalOld.png") +image_2 = Image.open("./winCalNew.png") + +difference_image = vision.extract_difference_image(image_1, image_2, tolerance=0.05) +difference_image.show(title="difference Image") \ No newline at end of file diff --git a/openadapt/vision.py b/openadapt/vision.py index 3824203b0..78c6be4f7 100644 --- a/openadapt/vision.py +++ b/openadapt/vision.py @@ -127,30 +127,6 @@ def refine_masks(masks: list[np.ndarray]) -> list[np.ndarray]: logger.info(f"{len(refined_masks)=}") return refined_masks -@cache.cache() -def extract_difference_image( - new_image: Image.Image, - old_image: Image.Image, - tolerance: float = 0.05, -) -> Image.Image: - """Extract the portion of the new image that is different from the old image.""" - new_image_np = np.array(new_image.convert('L')) - old_image_np = np.array(old_image.convert('L')) - - # Compute the absolute difference between the two images - diff = np.abs(new_image_np - old_image_np) - - # Create a mask for the regions where the difference is above the tolerance - mask = diff > (255 * tolerance) - - # Initialize an array for the difference image - diff_image_np = np.zeros_like(new_image_np) - - # Set the pixels that are different in the new image - diff_image_np[mask] = new_image_np[mask] - - # Convert the numpy array back to an image - return Image.fromarray(diff_image_np) @cache.cache() def filter_thin_ragged_masks( @@ -318,7 +294,6 @@ def calculate_bounding_boxes( return bounding_boxes, centroids - def get_image_similarity( im1: Image.Image, im2: Image.Image, @@ -537,4 +512,4 @@ def filter_ui_components( ) filtered_masks.append(contour_mask) return filtered_masks -""" +""" \ No newline at end of file From 383002a63811c68734b83372e3befe0db23f9ba8 Mon Sep 17 00:00:00 2001 From: Animesh404 Date: Mon, 24 Jun 2024 03:49:05 +0530 Subject: [PATCH 5/9] testing visual strategy --- openadapt/strategies/visual.py | 167 +++++++++++++++++++-------------- openadapt/vision.py | 37 +++++++- 2 files changed, 130 insertions(+), 74 deletions(-) diff --git a/openadapt/strategies/visual.py b/openadapt/strategies/visual.py index 07e27c60a..4d5fa16d7 100644 --- a/openadapt/strategies/visual.py +++ b/openadapt/strategies/visual.py @@ -47,11 +47,20 @@ from dataclasses import dataclass from pprint import pformat import time -from turtle import title + from loguru import logger from PIL import Image, ImageDraw import numpy as np -from openadapt import adapters, common, models, plotting, strategies, utils, vision + +from openadapt import ( + adapters, + common, + models, + plotting, + strategies, + utils, + vision, +) DEBUG = False DEBUG_REPLAY = False @@ -67,6 +76,7 @@ class Segmentation: Attributes: image: The original image used to generate segments. + marked_image: The marked image (for Set-of-Mark prompting). masked_images: A list of PIL Image objects that have been masked based on segmentation. descriptions: Descriptions of each segmented region, correlating with each @@ -80,6 +90,7 @@ class Segmentation: """ image: Image.Image + marked_image: Image.Image masked_images: list[Image.Image] descriptions: list[str] bounding_boxes: list[dict[str, float]] # "top", "left", "height", "width" @@ -111,6 +122,7 @@ def add_active_segment_descriptions(action_events: list[models.ActionEvent]) -> def apply_replay_instructions( action_events: list[models.ActionEvent], replay_instructions: str, + # retain_window_events: bool = False, ) -> None: """Modify the given ActionEvents according to the given replay instructions. @@ -131,7 +143,7 @@ def apply_replay_instructions( prompt_adapter = adapters.get_default_prompt_adapter() content = prompt_adapter.prompt( prompt, - system_prompt, + system_prompt=system_prompt, ) content_dict = utils.parse_code_snippet(content) try: @@ -166,6 +178,7 @@ def __init__( """ super().__init__(recording) self.recording_action_idx = 0 + self.action_history = [] add_active_segment_descriptions(recording.processed_action_events) self.modified_actions = apply_replay_instructions( recording.processed_action_events, @@ -234,8 +247,16 @@ def get_next_action_event( target_mouse_y = target_centroid[1] / height_ratio + active_window.top modified_reference_action.mouse_x = target_mouse_x modified_reference_action.mouse_y = target_mouse_y + self.action_history.append(modified_reference_action) return modified_reference_action + def __del__(self) -> None: + """Log the action history.""" + action_history_dicts = [ + action.to_prompt_dict() for action in self.action_history + ] + logger.info(f"action_history=\n{pformat(action_history_dicts)}") + def get_active_segment( action: models.ActionEvent, @@ -352,26 +373,25 @@ def find_similar_image_segmentation( return similar_segmentation, similar_segmentation_diff - + def combine_segmentations( new_image: Image.Image, previous_segmentation: Segmentation, new_descriptions: list[str], new_masked_images: list[Image.Image], - new_masks: list[np.ndarray] + new_masks: list[np.ndarray], ) -> Segmentation: """Combine the previous segmentation with the new segmentation of the differences. - Args: new_image: The new image which includes the changes. previous_segmentation: The previous segmentation containing unchanged segments. new_descriptions: Descriptions of the new segments from the difference image. new_masked_images: Masked images of the new segments from the difference image. new_masks: masks of the new segments. - Returns: Segmentation: A new segmentation combining both previous and new segments. """ + def masks_overlap(mask1, mask2): """Check if two masks overlap.""" return np.any(np.logical_and(mask1, mask2)) @@ -386,9 +406,15 @@ def masks_overlap(mask1, mask2): filtered_previous_centroids = [] for idx, prev_mask in enumerate(previous_segmentation.masks): if not any(masks_overlap(prev_mask, new_mask) for new_mask in new_masks): - filtered_previous_masked_images.append(previous_segmentation.masked_images[idx]) - filtered_previous_descriptions.append(previous_segmentation.descriptions[idx]) - filtered_previous_bounding_boxes.append(previous_segmentation.bounding_boxes[idx]) + filtered_previous_masked_images.append( + previous_segmentation.masked_images[idx] + ) + filtered_previous_descriptions.append( + previous_segmentation.descriptions[idx] + ) + filtered_previous_bounding_boxes.append( + previous_segmentation.bounding_boxes[idx] + ) filtered_previous_centroids.append(previous_segmentation.centroids[idx]) # Combine filtered previous segments with new segments @@ -402,7 +428,7 @@ def masks_overlap(mask1, mask2): masked_images=combined_masked_images, descriptions=combined_descriptions, bounding_boxes=combined_bounding_boxes, - centroids=combined_centroids + centroids=combined_centroids, ) @@ -493,8 +519,13 @@ def get_window_segmentation( len(descriptions), len(centroids), ) + marked_image = plotting.get_marked_image( + original_image, + refined_masks, # masks, + ) segmentation = Segmentation( original_image, + marked_image, masked_images, descriptions, bounding_boxes, @@ -524,72 +555,62 @@ def prompt_for_descriptions( Returns: list of descriptions for each masked image. """ - prompt_adapter = adapters.get_default_prompt_adapter() + # TODO: move inside adapters.prompt + for driver in adapters.prompt.DRIVER_ORDER: + # off by one to account for original image + if driver.MAX_IMAGES and (len(masked_images) + 1 > driver.MAX_IMAGES): + masked_images_batches = utils.split_list( + masked_images, + driver.MAX_IMAGES - 1, + ) + descriptions = [] + for masked_images_batch in masked_images_batches: + descriptions_batch = prompt_for_descriptions( + original_image, + masked_images_batch, + active_segment_description, + exceptions, + ) + descriptions += descriptions_batch + return descriptions - # TODO: move inside adapters - # off by one to account for original image - if prompt_adapter.MAX_IMAGES and ( - len(masked_images) + 1 > prompt_adapter.MAX_IMAGES - ): - masked_images_batches = utils.split_list( - masked_images, - prompt_adapter.MAX_IMAGES - 1, + images = [original_image] + masked_images + system_prompt = utils.render_template_from_file( + "prompts/system.j2", ) - descriptions = [] - for masked_images_batch in masked_images_batches: - descriptions_batch = prompt_for_descriptions( + logger.info(f"system_prompt=\n{system_prompt}") + num_segments = len(masked_images) + prompt = utils.render_template_from_file( + "prompts/description.j2", + active_segment_description=active_segment_description, + num_segments=num_segments, + exceptions=exceptions, + ).strip() + logger.info(f"prompt=\n{prompt}") + logger.info(f"{len(images)=}") + descriptions_json = driver.prompt( + prompt, + system_prompt, + images, + ) + descriptions = utils.parse_code_snippet(descriptions_json)["descriptions"] + logger.info(f"{descriptions=}") + try: + assert len(descriptions) == len(masked_images), ( + len(descriptions), + len(masked_images), + ) + except Exception as exc: + exceptions = exceptions or [] + exceptions.append(exc) + logger.info(f"exceptions=\n{pformat(exceptions)}") + return prompt_for_descriptions( original_image, - masked_images_batch, + masked_images, active_segment_description, exceptions, ) - descriptions += descriptions_batch - return descriptions - - images = [original_image] + masked_images - system_prompt = utils.render_template_from_file( - "prompts/system.j2", - ) - logger.info(f"system_prompt=\n{system_prompt}") - num_segments = len(masked_images) - prompt = utils.render_template_from_file( - "prompts/description.j2", - active_segment_description=active_segment_description, - num_segments=num_segments, - exceptions=exceptions, - ) - logger.info(f"prompt=\n{prompt}") - logger.info(f"{len(images)=}") - descriptions_json = prompt_adapter.prompt( - prompt, - system_prompt, - images, - ) - descriptions = utils.parse_code_snippet(descriptions_json)["descriptions"] - logger.info(f"{descriptions=}") - try: - assert len(descriptions) == len(masked_images), ( - len(descriptions), - len(masked_images), - ) - except Exception as exc: - exceptions = exceptions or [] - exceptions.append(exc) - logger.info(f"exceptions=\n{pformat(exceptions)}") - return prompt_for_descriptions( - original_image, - masked_images, - active_segment_description, - exceptions, - ) - - # remove indexes - descriptions = [desc for idx, desc in descriptions] - return descriptions -#Example usage for visualizing -image_1 = Image.open("./winCalOld.png") -image_2 = Image.open("./winCalNew.png") - -difference_image = vision.extract_difference_image(image_1, image_2, tolerance=0.05) -difference_image.show(title="difference Image") \ No newline at end of file + # remove indexes + descriptions = [desc for idx, desc in descriptions] + return descriptions diff --git a/openadapt/vision.py b/openadapt/vision.py index 78c6be4f7..8329f4bb8 100644 --- a/openadapt/vision.py +++ b/openadapt/vision.py @@ -56,6 +56,41 @@ def get_masks_from_segmented_image( return masks +@cache.cache() +def extract_difference_image( + new_image: Image.Image, + old_image: Image.Image, + tolerance: float = 0.05, +) -> Image.Image: + """Extract the portion of the new image that is different from the old image. + + Args: + new_image: The new image as a PIL Image object. + old_image: The old image as a PIL Image object. + tolerance: Tolerance level to consider a pixel as different (default is 0.05). + + Returns: + A PIL Image object representing the difference image. + """ + new_image_np = np.array(new_image) + old_image_np = np.array(old_image) + + # Compute the absolute difference between the two images in each color channel + diff = np.abs(new_image_np - old_image_np) + + # Create a mask for the regions where the difference is above the tolerance + mask = np.any(diff > (255 * tolerance), axis=-1) + + # Initialize an array for the segmented image + segmented_image_np = np.zeros_like(new_image_np) + + # Set the pixels that are different in the new image + segmented_image_np[mask] = new_image_np[mask] + + # Convert the numpy array back to an image + return Image.fromarray(segmented_image_np) + + @cache.cache() def filter_masks_by_size( masks: list[np.ndarray], @@ -512,4 +547,4 @@ def filter_ui_components( ) filtered_masks.append(contour_mask) return filtered_masks -""" \ No newline at end of file +""" From 713fb9de04692f5581d3827af10922825fbadf2c Mon Sep 17 00:00:00 2001 From: Animesh404 Date: Fri, 28 Jun 2024 01:55:08 +0530 Subject: [PATCH 6/9] WIP avoid unnecessary-segmentation --- openadapt/strategies/visual.py | 114 ++++++++++----------------------- openadapt/vision.py | 45 ++++++------- openadapt/window/__init__.py | 2 +- 3 files changed, 57 insertions(+), 104 deletions(-) diff --git a/openadapt/strategies/visual.py b/openadapt/strategies/visual.py index 4d5fa16d7..b3cdf9827 100644 --- a/openadapt/strategies/visual.py +++ b/openadapt/strategies/visual.py @@ -374,64 +374,6 @@ def find_similar_image_segmentation( return similar_segmentation, similar_segmentation_diff -def combine_segmentations( - new_image: Image.Image, - previous_segmentation: Segmentation, - new_descriptions: list[str], - new_masked_images: list[Image.Image], - new_masks: list[np.ndarray], -) -> Segmentation: - """Combine the previous segmentation with the new segmentation of the differences. - Args: - new_image: The new image which includes the changes. - previous_segmentation: The previous segmentation containing unchanged segments. - new_descriptions: Descriptions of the new segments from the difference image. - new_masked_images: Masked images of the new segments from the difference image. - new_masks: masks of the new segments. - Returns: - Segmentation: A new segmentation combining both previous and new segments. - """ - - def masks_overlap(mask1, mask2): - """Check if two masks overlap.""" - return np.any(np.logical_and(mask1, mask2)) - - # Calculate the bounding boxes and centroids for the new segments - new_bounding_boxes, new_centroids = vision.calculate_bounding_boxes(new_masks) - - # Filter out overlapping previous segments - filtered_previous_masked_images = [] - filtered_previous_descriptions = [] - filtered_previous_bounding_boxes = [] - filtered_previous_centroids = [] - for idx, prev_mask in enumerate(previous_segmentation.masks): - if not any(masks_overlap(prev_mask, new_mask) for new_mask in new_masks): - filtered_previous_masked_images.append( - previous_segmentation.masked_images[idx] - ) - filtered_previous_descriptions.append( - previous_segmentation.descriptions[idx] - ) - filtered_previous_bounding_boxes.append( - previous_segmentation.bounding_boxes[idx] - ) - filtered_previous_centroids.append(previous_segmentation.centroids[idx]) - - # Combine filtered previous segments with new segments - combined_masked_images = filtered_previous_masked_images + new_masked_images - combined_descriptions = filtered_previous_descriptions + new_descriptions - combined_bounding_boxes = filtered_previous_bounding_boxes + new_bounding_boxes - combined_centroids = filtered_previous_centroids + new_centroids - - return Segmentation( - image=new_image, - masked_images=combined_masked_images, - descriptions=combined_descriptions, - bounding_boxes=combined_bounding_boxes, - centroids=combined_centroids, - ) - - def get_window_segmentation( action_event: models.ActionEvent, exceptions: list[Exception] | None = None, @@ -460,28 +402,42 @@ def get_window_segmentation( # TODO XXX: create copy of similar_segmentation, but overwrite with segments of # regions of new image where segments of similar_segmentation overlap non-zero # regions of similar_segmentation_diff - difference_image = vision.extract_difference_image( - original_image, - similar_segmentation.image, - tolerance=0.05, - ) - new_masks = vision.get_masks_from_segmented_image(difference_image) - new_masked_images = vision.extract_masked_images(difference_image, new_masks) - new_descriptions = prompt_for_descriptions( - difference_image, - new_masked_images, - action_event.active_segment_description, - exceptions, - ) - updated_segmentation = combine_segmentations( - difference_image, - similar_segmentation, - new_descriptions, - new_masked_images, - new_masks, + # Create a copy of the similar_segmentation + logger.info(f"Found similar_segmentation") + modified_original_image = similar_segmentation.image.copy() + modified_marked_image = similar_segmentation.marked_image.copy() + modified_masked_images = [ + masked_image.copy() for masked_image in similar_segmentation.masked_images + ] + modified_descriptions = similar_segmentation.descriptions[:] + modified_bounding_boxes = similar_segmentation.bounding_boxes[:] + modified_centroids = similar_segmentation.centroids[:] + + # Extract the masks from similar_segmentation_diff + masks_diff = vision.get_masks_from_segmented_image(similar_segmentation_diff) + refined_masks_diff = vision.refine_masks(masks_diff) + + # Iterate through each mask in similar_segmentation and modify it based on masks_diff + for mask_idx, mask in enumerate(similar_segmentation.masked_images): + overlap_mask = vision.get_overlap_mask(mask, refined_masks_diff[mask_idx]) + if overlap_mask.max() > 0: + modified_masked_images[mask_idx] = vision.extract_masked_images( + original_image, [overlap_mask] + )[0] + + if DEBUG: + similar_segmentation.image.show() + + modified_segmentation = Segmentation( + modified_original_image, + modified_marked_image, + modified_masked_images, + modified_descriptions, + modified_bounding_boxes, + modified_centroids, ) - similar_segmentation = updated_segmentation - return similar_segmentation + # Return the modified segmentation object + return modified_segmentation segmentation_adapter = adapters.get_default_segmentation_adapter() segmented_image = segmentation_adapter.fetch_segmented_image(original_image) diff --git a/openadapt/vision.py b/openadapt/vision.py index 8329f4bb8..cb98ef31f 100644 --- a/openadapt/vision.py +++ b/openadapt/vision.py @@ -57,38 +57,35 @@ def get_masks_from_segmented_image( @cache.cache() -def extract_difference_image( - new_image: Image.Image, - old_image: Image.Image, - tolerance: float = 0.05, -) -> Image.Image: - """Extract the portion of the new image that is different from the old image. +def get_overlap_mask(original_mask: Image.Image, diff_mask: Image.Image) -> Image.Image: + """Calculates the overlap between the original mask and the difference mask. Args: - new_image: The new image as a PIL Image object. - old_image: The old image as a PIL Image object. - tolerance: Tolerance level to consider a pixel as different (default is 0.05). + original_mask: The original mask image. + diff_mask: The difference mask image. Returns: - A PIL Image object representing the difference image. + An image representing the overlap mask. """ - new_image_np = np.array(new_image) - old_image_np = np.array(old_image) - - # Compute the absolute difference between the two images in each color channel - diff = np.abs(new_image_np - old_image_np) - - # Create a mask for the regions where the difference is above the tolerance - mask = np.any(diff > (255 * tolerance), axis=-1) + # Convert images to numpy arrays for easier manipulation + original_mask_np = np.array(original_mask) + diff_mask_np = np.array(diff_mask) + + # Ensure the shapes of the masks are compatible by resizing + if original_mask_np.shape != diff_mask_np.shape: + logger.warning( + f"Mask shapes are different. Resizing diff_mask from {diff_mask_np.shape} to {original_mask_np.shape}" + ) + diff_mask = diff_mask.resize(original_mask.size, Image.NEAREST) + diff_mask_np = np.array(diff_mask) - # Initialize an array for the segmented image - segmented_image_np = np.zeros_like(new_image_np) + # Calculate the overlap: non-zero regions in both masks + overlap_mask_np = np.logical_and(original_mask_np > 0, diff_mask_np > 0) - # Set the pixels that are different in the new image - segmented_image_np[mask] = new_image_np[mask] + # Convert the overlap mask back to an image + overlap_mask = Image.fromarray((overlap_mask_np * 255).astype(np.uint8)) - # Convert the numpy array back to an image - return Image.fromarray(segmented_image_np) + return overlap_mask @cache.cache() diff --git a/openadapt/window/__init__.py b/openadapt/window/__init__.py index 99b42ec02..ebde66d3f 100644 --- a/openadapt/window/__init__.py +++ b/openadapt/window/__init__.py @@ -33,7 +33,7 @@ def get_active_window_data( """ state = get_active_window_state(include_window_data) if not state: - return None + return {} title = state["title"] left = state["left"] top = state["top"] From 331fcca2623c06f83aba535e1303735fd663e4f6 Mon Sep 17 00:00:00 2001 From: Animesh404 Date: Fri, 28 Jun 2024 20:30:46 +0530 Subject: [PATCH 7/9] WIP combining previous and difference images when similar_segmentation is found --- experiments/visualizing_segments.py | 191 ++++++++++++++++++++++++++++ experiments/winCalNew.png | Bin 0 -> 16522 bytes experiments/winCalOld.png | Bin 0 -> 16201 bytes openadapt/strategies/visual.py | 139 +++++++++++++++----- openadapt/vision.py | 32 ----- 5 files changed, 297 insertions(+), 65 deletions(-) create mode 100644 experiments/visualizing_segments.py create mode 100644 experiments/winCalNew.png create mode 100644 experiments/winCalOld.png diff --git a/experiments/visualizing_segments.py b/experiments/visualizing_segments.py new file mode 100644 index 000000000..6b415320c --- /dev/null +++ b/experiments/visualizing_segments.py @@ -0,0 +1,191 @@ +from loguru import logger +from PIL import Image +import numpy as np +from openadapt import vision +import cv2 +from skimage.metrics import structural_similarity as ssim + + +def extract_difference_image( + new_image: Image.Image, + old_image: Image.Image, + tolerance: float = 0.05, +) -> Image.Image: + """Extract the portion of the new image that is different from the old image. + + Args: + new_image: The new image as a PIL Image object. + old_image: The old image as a PIL Image object. + tolerance: Tolerance level to consider a pixel as different (default is 0.05). + + Returns: + A PIL Image object representing the difference image. + """ + new_image_np = np.array(new_image) + old_image_np = np.array(old_image) + + # Compute the absolute difference between the two images in each color channel + diff = np.abs(new_image_np - old_image_np) + + # Create a mask for the regions where the difference is above the tolerance + mask = np.any(diff > (255 * tolerance), axis=-1) + + # Initialize an array for the segmented image + segmented_image_np = np.zeros_like(new_image_np) + + # Set the pixels that are different in the new image + segmented_image_np[mask] = new_image_np[mask] + + # Convert the numpy array back to an image + return Image.fromarray(segmented_image_np) + + +def combine_images_with_masks( + image_1: Image.Image, + difference_image: Image.Image, + old_masks: list[np.ndarray], + new_masks: list[np.ndarray], +) -> Image.Image: + """Combine image_1 and difference_image using the masks. + + Args: + image_1: The original image as a PIL Image object. + difference_image: The difference image as a PIL Image object. + old_masks: List of numpy arrays representing the masks from the original image. + new_masks: List of numpy arrays representing the masks from the difference image. + + Returns: + A PIL Image object representing the combined image. + """ + + image_1_np = np.array(image_1) + difference_image_np = np.array(difference_image) + + # Create an empty canvas with the same dimensions and mode as image_1 + combined_image_np = np.zeros_like(image_1_np) + + def masks_overlap(mask1, mask2): + """Check if two masks overlap.""" + return np.any(np.logical_and(mask1, mask2)) + + # Apply old masks to the combined image where there is no overlap with new masks + for old_mask in old_masks: + if not any(masks_overlap(old_mask, new_mask) for new_mask in new_masks): + combined_image_np[old_mask] = image_1_np[old_mask] + + # Apply new masks to the combined image + for new_mask in new_masks: + combined_image_np[new_mask] = difference_image_np[new_mask] + + # Fill in remaining pixels from image_1 where there are no masks + combined_image_np[(combined_image_np == 0).all(axis=-1)] = image_1_np[ + (combined_image_np == 0).all(axis=-1) + ] + + # Convert the numpy array back to an image + return Image.fromarray(combined_image_np) + + +def find_matching_sections_ssim( + image_1: Image.Image, + image_2: Image.Image, + block_size: int = 50, + threshold: float = 0.9, +): + """Find and visualize matching sections between two images using SSIM. + + Args: + image_1: The first image as a PIL Image object. + image_2: The second image as a PIL Image object. + block_size: The size of the blocks to compare in the SSIM calculation. Default is 50. + threshold: The SSIM score threshold to consider blocks as matching. Default is 0.9. + + Returns: + A PIL Image object with matching sections highlighted. + """ + + # Convert images to grayscale + image_1_gray = np.array(image_1.convert("L")) + image_2_gray = np.array(image_2.convert("L")) + + # Dimensions of the images + height, width = image_1_gray.shape + + # Create an empty image to visualize matches + matching_image = np.zeros_like(image_1_gray) + + # Iterate over the image in blocks + for y in range(0, height, block_size): + for x in range(0, width, block_size): + # Define the block region + block_1 = image_1_gray[y : y + block_size, x : x + block_size] + block_2 = image_2_gray[y : y + block_size, x : x + block_size] + + # Check if blocks have the same shape + if block_1.shape == block_2.shape: + # Compute SSIM for the current block + score, _ = ssim(block_1, block_2, full=True) + + # Highlight matching sections + if score >= threshold: + matching_image[y : y + block_size, x : x + block_size] = 255 + + # Convert matching regions back to RGB or RGBA for visualization + if image_1.mode == "RGBA": + matching_image_rgb = cv2.cvtColor(matching_image, cv2.COLOR_GRAY2RGBA) + highlight_color = [255, 0, 0, 255] # Red with full opacity for RGBA + else: + matching_image_rgb = cv2.cvtColor(matching_image, cv2.COLOR_GRAY2RGB) + highlight_color = [255, 0, 0] # Red for RGB + + # Create an overlay to highlight matching regions on the original image + overlay = np.array(image_1) + overlay[matching_image == 255] = highlight_color + + # Convert back to PIL Image + matching_image_pil = Image.fromarray(overlay) + + return matching_image_pil + + +def visualize(image_1: Image, image_2: Image): + """Visualize matching sections, difference sections, and combined images between two images. + + Args: + image_1: The first image as a PIL Image object. + image_2: The second image as a PIL Image object. + + Returns: + None + """ + + try: + images = [] + + images.append(image_1) + + matching_image = find_matching_sections_ssim(image_1, image_2) + images.append(matching_image) + + difference_image = extract_difference_image(image_2, image_1, tolerance=0.05) + images.append(difference_image) + + old_masks = vision.get_masks_from_segmented_image(image_1) + new_masks = vision.get_masks_from_segmented_image(difference_image) + + combined_image = combine_images_with_masks( + image_1, difference_image, old_masks, new_masks + ) + images.append(combined_image) + + for image in images: + image.show() + + except Exception as e: + logger.error(f"An error occurred: {e}") + + +# Example usage +img_1 = Image.open("./winCalOld.png") +img_2 = Image.open("./winCalNew.png") +visualize(img_1, img_2) diff --git a/experiments/winCalNew.png b/experiments/winCalNew.png new file mode 100644 index 0000000000000000000000000000000000000000..90d66f7712968417ac1c7c3f7d98a8224fdf0c5e GIT binary patch literal 16522 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 literal 0 HcmV?d00001 diff --git a/experiments/winCalOld.png b/experiments/winCalOld.png new file mode 100644 index 0000000000000000000000000000000000000000..61b56534e0f3b1a85dbaaad851080b3da2000f64 GIT binary patch literal 16201 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 Segmentation: + """Combine the previous segmentation with the new segmentation of the differences. + Args: + difference_image: The difference image found in similar segmentation. + previous_segmentation: The previous segmentation containing unchanged segments. + new_descriptions: Descriptions of the new segments from the difference image. + new_masked_images: Masked images of the new segments from the difference image. + new_masks: masks of the new segments. + Returns: + Segmentation: A new segmentation combining both previous and new segments. + """ + + image_1_np = np.array(previous_segmentation.image) + difference_image_np = np.array(difference_image) + + # Create an empty canvas with the same dimensions and mode as image_1 + combined_image_np = np.zeros_like(image_1_np) + + def masks_overlap(mask1, mask2): + """Check if two masks overlap.""" + return np.any(np.logical_and(mask1, mask2)) + + # Calculate the bounding boxes and centroids for the new segments + new_bounding_boxes, new_centroids = vision.calculate_bounding_boxes(new_masks) + + previous_masks = vision.get_masks_from_segmented_image(previous_segmentation.image) + + # Filter out overlapping previous segments + filtered_previous_masked_images = [] + filtered_previous_descriptions = [] + filtered_previous_bounding_boxes = [] + filtered_previous_centroids = [] + for idx, prev_mask in enumerate(previous_masks): + if not any(masks_overlap(prev_mask, new_mask) for new_mask in new_masks): + combined_image_np[prev_mask] = image_1_np[ + prev_mask + ] # Apply previous masks to the combined image where there is no overlap with new masks + filtered_previous_masked_images.append( + previous_segmentation.masked_images[idx] + ) + filtered_previous_descriptions.append( + previous_segmentation.descriptions[idx] + ) + filtered_previous_bounding_boxes.append( + previous_segmentation.bounding_boxes[idx] + ) + filtered_previous_centroids.append(previous_segmentation.centroids[idx]) + + # Apply new masks to the combined image + for new_mask in new_masks: + combined_image_np[new_mask] = difference_image_np[new_mask] + + # Fill in remaining pixels from image_1 where there are no masks + combined_image_np[(combined_image_np == 0).all(axis=-1)] = image_1_np[ + (combined_image_np == 0).all(axis=-1) + ] + + # Combine filtered previous segments with new segments + combined_masked_images = filtered_previous_masked_images + new_masked_images + combined_descriptions = filtered_previous_descriptions + new_descriptions + combined_bounding_boxes = filtered_previous_bounding_boxes + new_bounding_boxes + combined_centroids = filtered_previous_centroids + new_centroids + + # Convert the numpy array back to an image + new_image = Image.fromarray(combined_image_np) + + marked_image = plotting.get_marked_image( + new_image, + new_masks, # masks, + ) + + return Segmentation( + new_image, + marked_image, + combined_masked_images, + combined_descriptions, + combined_bounding_boxes, + combined_centroids, + ) + + def get_window_segmentation( action_event: models.ActionEvent, exceptions: list[Exception] | None = None, @@ -404,40 +492,24 @@ def get_window_segmentation( # regions of similar_segmentation_diff # Create a copy of the similar_segmentation logger.info(f"Found similar_segmentation") - modified_original_image = similar_segmentation.image.copy() - modified_marked_image = similar_segmentation.marked_image.copy() - modified_masked_images = [ - masked_image.copy() for masked_image in similar_segmentation.masked_images - ] - modified_descriptions = similar_segmentation.descriptions[:] - modified_bounding_boxes = similar_segmentation.bounding_boxes[:] - modified_centroids = similar_segmentation.centroids[:] - - # Extract the masks from similar_segmentation_diff - masks_diff = vision.get_masks_from_segmented_image(similar_segmentation_diff) - refined_masks_diff = vision.refine_masks(masks_diff) - - # Iterate through each mask in similar_segmentation and modify it based on masks_diff - for mask_idx, mask in enumerate(similar_segmentation.masked_images): - overlap_mask = vision.get_overlap_mask(mask, refined_masks_diff[mask_idx]) - if overlap_mask.max() > 0: - modified_masked_images[mask_idx] = vision.extract_masked_images( - original_image, [overlap_mask] - )[0] - - if DEBUG: - similar_segmentation.image.show() - - modified_segmentation = Segmentation( - modified_original_image, - modified_marked_image, - modified_masked_images, - modified_descriptions, - modified_bounding_boxes, - modified_centroids, + new_masks = vision.get_masks_from_segmented_image(similar_segmentation_diff) + new_masked_images = vision.extract_masked_images( + similar_segmentation_diff, new_masks ) - # Return the modified segmentation object - return modified_segmentation + new_descriptions = prompt_for_descriptions( + similar_segmentation_diff, + new_masked_images, + action_event.active_segment_description, + exceptions, + ) + updated_segmentation = combine_segmentations( + similar_segmentation_diff, + similar_segmentation, + new_descriptions, + new_masked_images, + new_masks, + ) + return updated_segmentation segmentation_adapter = adapters.get_default_segmentation_adapter() segmented_image = segmentation_adapter.fetch_segmented_image(original_image) @@ -479,6 +551,7 @@ def get_window_segmentation( original_image, refined_masks, # masks, ) + segmentation = Segmentation( original_image, marked_image, diff --git a/openadapt/vision.py b/openadapt/vision.py index cb98ef31f..65c3660ec 100644 --- a/openadapt/vision.py +++ b/openadapt/vision.py @@ -56,38 +56,6 @@ def get_masks_from_segmented_image( return masks -@cache.cache() -def get_overlap_mask(original_mask: Image.Image, diff_mask: Image.Image) -> Image.Image: - """Calculates the overlap between the original mask and the difference mask. - - Args: - original_mask: The original mask image. - diff_mask: The difference mask image. - - Returns: - An image representing the overlap mask. - """ - # Convert images to numpy arrays for easier manipulation - original_mask_np = np.array(original_mask) - diff_mask_np = np.array(diff_mask) - - # Ensure the shapes of the masks are compatible by resizing - if original_mask_np.shape != diff_mask_np.shape: - logger.warning( - f"Mask shapes are different. Resizing diff_mask from {diff_mask_np.shape} to {original_mask_np.shape}" - ) - diff_mask = diff_mask.resize(original_mask.size, Image.NEAREST) - diff_mask_np = np.array(diff_mask) - - # Calculate the overlap: non-zero regions in both masks - overlap_mask_np = np.logical_and(original_mask_np > 0, diff_mask_np > 0) - - # Convert the overlap mask back to an image - overlap_mask = Image.fromarray((overlap_mask_np * 255).astype(np.uint8)) - - return overlap_mask - - @cache.cache() def filter_masks_by_size( masks: list[np.ndarray], From ff5675d82e3db717c6354f01d31e8d49016cbf45 Mon Sep 17 00:00:00 2001 From: Animesh404 Date: Sat, 29 Jun 2024 21:13:58 +0530 Subject: [PATCH 8/9] visualize segments --- experiments/visualizing_segments.py | 54 ++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/experiments/visualizing_segments.py b/experiments/visualizing_segments.py index 6b415320c..68de27ea1 100644 --- a/experiments/visualizing_segments.py +++ b/experiments/visualizing_segments.py @@ -1,7 +1,7 @@ from loguru import logger from PIL import Image import numpy as np -from openadapt import vision +from openadapt import vision, adapters import cv2 from skimage.metrics import structural_similarity as ssim @@ -130,17 +130,18 @@ def find_matching_sections_ssim( if score >= threshold: matching_image[y : y + block_size, x : x + block_size] = 255 - # Convert matching regions back to RGB or RGBA for visualization - if image_1.mode == "RGBA": - matching_image_rgb = cv2.cvtColor(matching_image, cv2.COLOR_GRAY2RGBA) - highlight_color = [255, 0, 0, 255] # Red with full opacity for RGBA - else: - matching_image_rgb = cv2.cvtColor(matching_image, cv2.COLOR_GRAY2RGB) - highlight_color = [255, 0, 0] # Red for RGB - # Create an overlay to highlight matching regions on the original image - overlay = np.array(image_1) - overlay[matching_image == 255] = highlight_color + overlay = np.zeros_like(np.array(image_1), dtype=np.uint8) + + # Apply the overlay to the matching regions + for c in range(0, 3): # For each color channel + overlay[:, :, c] = np.where( + matching_image == 255, np.array(image_1)[:, :, c], 0 + ) + + # For RGBA images, set the alpha channel to 255 (fully opaque) for matching sections + if image_1.mode == "RGBA": + overlay[:, :, 3] = np.where(matching_image == 255, 255, 0) # Convert back to PIL Image matching_image_pil = Image.fromarray(overlay) @@ -162,13 +163,9 @@ def visualize(image_1: Image, image_2: Image): try: images = [] - images.append(image_1) - matching_image = find_matching_sections_ssim(image_1, image_2) - images.append(matching_image) difference_image = extract_difference_image(image_2, image_1, tolerance=0.05) - images.append(difference_image) old_masks = vision.get_masks_from_segmented_image(image_1) new_masks = vision.get_masks_from_segmented_image(difference_image) @@ -176,7 +173,30 @@ def visualize(image_1: Image, image_2: Image): combined_image = combine_images_with_masks( image_1, difference_image, old_masks, new_masks ) + + segmentation_adapter = adapters.get_default_segmentation_adapter() + ref_segmented_image = segmentation_adapter.fetch_segmented_image(image_1) + new_segmented_image = segmentation_adapter.fetch_segmented_image(image_2) + matching_image_segment = segmentation_adapter.fetch_segmented_image( + matching_image + ) + non_matching_image_Segment = segmentation_adapter.fetch_segmented_image( + difference_image + ) + combined_image_segment = segmentation_adapter.fetch_segmented_image( + combined_image + ) + + images.append(image_1) + images.append(ref_segmented_image) + images.append(image_2) + images.append(new_segmented_image) + images.append(matching_image) + images.append(matching_image_segment) + images.append(difference_image) + images.append(non_matching_image_Segment) images.append(combined_image) + images.append(combined_image_segment) for image in images: image.show() @@ -186,6 +206,6 @@ def visualize(image_1: Image, image_2: Image): # Example usage -img_1 = Image.open("./winCalOld.png") -img_2 = Image.open("./winCalNew.png") +img_2 = Image.open("../experiments/winCalNew.png") +img_1 = Image.open("../experiments/winCalOld.png") visualize(img_1, img_2) From de84c466d71939aee1f46d17855cc1c7fb6d028e Mon Sep 17 00:00:00 2001 From: Animesh404 Date: Sat, 29 Jun 2024 23:46:12 +0530 Subject: [PATCH 9/9] trying same logic that I implemented in visualizing_segments --- openadapt/strategies/visual.py | 39 ++++++++++++++++++++++------------ openadapt/vision.py | 15 +++++++++---- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/openadapt/strategies/visual.py b/openadapt/strategies/visual.py index 5247a74b7..471bff415 100644 --- a/openadapt/strategies/visual.py +++ b/openadapt/strategies/visual.py @@ -45,7 +45,6 @@ """ from dataclasses import dataclass -from hmac import new from pprint import pformat import time @@ -399,6 +398,10 @@ def combine_segmentations( # Create an empty canvas with the same dimensions and mode as image_1 combined_image_np = np.zeros_like(image_1_np) + # Ensure difference_image_np is 3 channels + if difference_image_np.ndim == 2: # Grayscale image + difference_image_np = np.stack((difference_image_np,) * 3, axis=-1) + def masks_overlap(mask1, mask2): """Check if two masks overlap.""" return np.any(np.logical_and(mask1, mask2)) @@ -406,11 +409,15 @@ def masks_overlap(mask1, mask2): # Calculate the bounding boxes and centroids for the new segments new_bounding_boxes, new_centroids = vision.calculate_bounding_boxes(new_masks) - previous_masks = vision.get_masks_from_segmented_image(previous_segmentation.image) + segmentation_adapter = adapters.get_default_segmentation_adapter() + segmented_prev_image = segmentation_adapter.fetch_segmented_image( + previous_segmentation.image + ) + previous_masks = vision.get_masks_from_segmented_image(segmented_prev_image) # Filter out overlapping previous segments filtered_previous_masked_images = [] - filtered_previous_descriptions = [] + # filtered_previous_descriptions = [] filtered_previous_bounding_boxes = [] filtered_previous_centroids = [] for idx, prev_mask in enumerate(previous_masks): @@ -421,9 +428,9 @@ def masks_overlap(mask1, mask2): filtered_previous_masked_images.append( previous_segmentation.masked_images[idx] ) - filtered_previous_descriptions.append( - previous_segmentation.descriptions[idx] - ) + # filtered_previous_descriptions.append( + # previous_segmentation.descriptions[idx] + # ) filtered_previous_bounding_boxes.append( previous_segmentation.bounding_boxes[idx] ) @@ -440,7 +447,7 @@ def masks_overlap(mask1, mask2): # Combine filtered previous segments with new segments combined_masked_images = filtered_previous_masked_images + new_masked_images - combined_descriptions = filtered_previous_descriptions + new_descriptions + # combined_descriptions = filtered_previous_descriptions + new_descriptions combined_bounding_boxes = filtered_previous_bounding_boxes + new_bounding_boxes combined_centroids = filtered_previous_centroids + new_centroids @@ -451,12 +458,13 @@ def masks_overlap(mask1, mask2): new_image, new_masks, # masks, ) + # new_image.show() return Segmentation( new_image, marked_image, combined_masked_images, - combined_descriptions, + new_descriptions, combined_bounding_boxes, combined_centroids, ) @@ -490,20 +498,24 @@ def get_window_segmentation( # TODO XXX: create copy of similar_segmentation, but overwrite with segments of # regions of new image where segments of similar_segmentation overlap non-zero # regions of similar_segmentation_diff - # Create a copy of the similar_segmentation logger.info(f"Found similar_segmentation") - new_masks = vision.get_masks_from_segmented_image(similar_segmentation_diff) + similar_segmentation_diff_image = Image.fromarray(similar_segmentation_diff) + segmentation_adapter = adapters.get_default_segmentation_adapter() + segmented_diff_image = segmentation_adapter.fetch_segmented_image( + similar_segmentation_diff_image + ) + new_masks = vision.get_masks_from_segmented_image(segmented_diff_image) new_masked_images = vision.extract_masked_images( - similar_segmentation_diff, new_masks + similar_segmentation_diff_image, new_masks ) new_descriptions = prompt_for_descriptions( - similar_segmentation_diff, + similar_segmentation_diff_image, new_masked_images, action_event.active_segment_description, exceptions, ) updated_segmentation = combine_segmentations( - similar_segmentation_diff, + similar_segmentation_diff_image, similar_segmentation, new_descriptions, new_masked_images, @@ -551,7 +563,6 @@ def get_window_segmentation( original_image, refined_masks, # masks, ) - segmentation = Segmentation( original_image, marked_image, diff --git a/openadapt/vision.py b/openadapt/vision.py index 65c3660ec..3f416b57c 100644 --- a/openadapt/vision.py +++ b/openadapt/vision.py @@ -237,11 +237,18 @@ def extract_masked_images( cropped_mask = mask[rmin : rmax + 1, cmin : cmax + 1] cropped_image = original_image_np[rmin : rmax + 1, cmin : cmax + 1] + # Ensure the cropped image has the correct shape + if cropped_image.ndim == 2: # Grayscale image + cropped_image = cropped_image[:, :, None] + elif cropped_image.shape[2] != 1: # Color image + cropped_image = cropped_image[:, :, :3] # Keep RGB channels only + + # Ensure the mask has the correct shape + reshaped_mask = cropped_mask[:, :, None] + # Apply the mask - masked_image = np.where(cropped_mask[:, :, None], cropped_image, 0).astype( - np.uint8 - ) - masked_images.append(Image.fromarray(masked_image)) + masked_image = np.where(reshaped_mask, cropped_image, 0).astype(np.uint8) + masked_images.append(Image.fromarray(masked_image.squeeze())) logger.info(f"{len(masked_images)=}") return masked_images