From d00ce3bd14066a984546d18690bb36fec54f17c7 Mon Sep 17 00:00:00 2001 From: Taiga Abe Date: Mon, 3 Mar 2025 23:02:00 -0800 Subject: [PATCH 01/34] started taking some notes on preprocessing. --- figures/CW44_naive_default_crop_img.png | Bin 0 -> 41429 bytes figures/CW44_naive_pre_crop_img.png | Bin 0 -> 62126 bytes figures/preprocessing_difference_alias.png | Bin 0 -> 97671 bytes notes/preprocessing.md | 13 +++++++++++++ 4 files changed, 13 insertions(+) create mode 100644 figures/CW44_naive_default_crop_img.png create mode 100644 figures/CW44_naive_pre_crop_img.png create mode 100644 figures/preprocessing_difference_alias.png create mode 100644 notes/preprocessing.md diff --git a/figures/CW44_naive_default_crop_img.png b/figures/CW44_naive_default_crop_img.png new file mode 100644 index 0000000000000000000000000000000000000000..ce119f9507f8dc286c358df2926fa3c61fd49cc9 GIT binary patch literal 41429 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
    )>a)taq7r)t5X4L0&2gdp9>jxBxWhiL59@BI_e3 z>D-|AVTl`kRmRW@)PLZ>qJ6 zie($0nvNC~TaxN}NAR>nhhQJZd4LpK`TPLi^f2ayq+2q713}OS+40Z&-=FJLktYgy z{HvC#bgmD-mE;OKOOo8O7%Zu7r2BNJTwG(tLQvH6)gfiduKKX=Lwo3R1|t2T*QTpN z_ZejszhJ(dXH9yr%uHrBc|wQ7F1;{U&O=jH2bq{Jb`$HRi9fS|8DT&p>XW~Cc^6@N zJaxW`dy1BGd4wNIEQMNAu@A{} zV4T(s8wbCwx-97a8&26$G6PC0iPFU~`Up;zaixeG*+gMFUZyxD+zPRJVK-;|Myen` zG+Osg)mFQOSvWs_U}af$i*U3uEqd4DBZ1Ozht=TIB(Wx(tt~52l}X&hhX>*x227!z zjW$;`{`eKcyrvv@KQ`!ppRt0%JTx=WcLuXNkL3{j30Y1E=XIytFJ~U70pwihplDKQ z0ywMq)dkx{4o%?+!Ub3pwGs8NTM?yFZ+>TU|Ib7MyUd+!gcTqZmPA7UQLzwnztQc? zU=XKh`BGuhr;Vhoy9QI5nxy`P4Fv(^xkoEen|v)Z%k8R(uKc=px5;Uq{ntejs0%~f z=$HJ)A-}MpH8*OQDMAD0i0f9ao{B5YtT+B5OgKg^W+gRg43#|KSG zGAhL;j_=ZtuV?hZ-AvlD9HD}jwRWg!tINV$a~FH~}*p>=t6}}r0zgZd6loZ^Z zp6SxAhY${s|MT_i^WQP!C=^27Z|mcv(+~r(69t+Q`Zd$dW-5S129C-v0L$m54#}!S z7NKkFcK=?}^tE{jBQGI%LX^1L@<{-t)*J+7{GqSUFwBY)(Sf`-Z{Cn>0IU#C7WE;| zcoe{vtl#sVJ}DmcRx`<9u$m*q+l7Bve;{R3F#hHjmBqdxkkD>-Bv4!_5`0ridUWem z>i!8Jj~1F1y@OP}@4j#0u%#0o(1x)Wh{xA{hTs-QZu}I?O}m%&wT<*Z;z^qtkiTrbzvraECq)8-^~oy|t5@dRx@M?c?6v44f{~ z_{t}ttMskc$uVg$Hsh(4M`CDfWIPM}H@nz&je{i-p8m7f8(w2-5y0xg+hz zIJJPIh>QiMxmN(N50wq%ay5WP%;An>ld31upyxxP&@3&k1|Gu(3Gk=1K1N^Y&m?`j0qzRfZQ#p@P85zQv@$RH_Fb^H^M5vuUvLzkn|8L*wbpx?ucGHtW5DIr=zawoVCER zj>!@08Xtt9#;7NuP6zv|_BY#N%t$t6kM@(C2XuRFOKexPV%U~p_ZXPcunA?(suQ|^ z{#CG`#2;`uo6v_328xD^yt1>QW23+**JG9?bm%Ji#9N+sJ?48r@@BA7&e4V;3Y8^q zJC#{cKLpn(ne>)ogWKf!HQu?kAKoyH5ZSky%F2C9)`*{SAvGnHOHx>vMok*WXR{nQ zvInY3&%>U+04r7-!zK*Oe@Kmx4>zwchL&FlF;C+I1Z}zz_$0Yye+o;iD-b7s7&2>urjf(pde+cjVle*YG zTK&VU+TG#`^uDYs6feIyGClFpcV7!7k)yfk!pOB1KQMGVVVm$7W5CqD?N#nFp*hfv zim#1A2R=_he4tkxrmP^3{A!{a_JKT64E%HyEX2jHeYaWt8V4;Bvxye6 zrL@xVWF}toUEpfmtIX3<`Ys)jk|rIDR!hvHB1xUK4 zaS)~GiYMY~)EDoh_2I#}Dw8!RgMSz2_sgBwhuzrR0kLhK$vJyS9Na+q~?@+w9nYYtO(&gDlDWtxczUg$IO1+D;gSBQrHV-ous? z>EEu(-+!Yx{l@249yJkSxbj}<4EDzZHM``Kyx@~qEpL)!sEil`tCiplJw;sw>s}r)-Pm9Om*&0kbbRZneW>N#LHN(cMj>n z-QRCWa`7()ytp9|NU}`q(S}cSMEw~Nx5cVO!(MaoP$~_L*NRc8r-tJY$G?lh(Ic50eeT|aJa5GAJMy_vf&BpSP9e0Ow zjmR^j&eW^qR$K7S1u?q~8Ha(1`E)>0OIT}wv!jYEZdhW=g`kw5MauL{c#KsmE~mDW zApq9TJ@utZR=dUwnccR6Sd@oi~uM3;EhU{e7b9}Q>t49I+wcH`J>f-m1!Laeh z;zY~5-X_reV(j|ab(vSAm|a?QXkB$*(7Z!7WvctQm(%iN)+`+DZ&{t%qHhujtcVrjLWURf1|%QO zLZ9AGJl;yiBF*!Ocb$mEPSbl)FJojJZcet+d(1vcsQW(Xl&I26)_sT}<(+!q-So-H z@!zNRSuWCU9S6|B=XVh&fq^d@ulMykTfDkP9Ly`)VGF$<&F-dB;A+KP3912$ZU-6v}m9>6394dJDGL`0R$ay^auyzIP!aD>JD)Ce5VKNTdv6zm@5e4SkF3U7w%SNH;(B`e7rotKq;j83 zvl?GiV9TW7nwRG(L#s?&S_WOJR424PoNmFU(U?%CaRwC1#?jGkM$$fswn^L}x zCcQKKsoe+qeJ)i_YI;DqW5SfrUkNgEg@b=78^*eBKDZ>p$urp-4O@CmNi=3{i%6$r zLSxe=mjNH@K_Z%D=5&6Fsu$oFcEEI{qGHGS`n*Lzd!CNLB(c0OK! zBe}<6iQ8vc+*unQ_o*(Y1S1KgQ@3~gx~v;G;2C%3)WfpmtaC(jz#V*WjTu+82g?MODO95MLe3Q{IFoAb490ttN*src%Z8VRcL{c={B z5G&1Fp1=+E=8K(ha^n4ZkT2&np23+ouoARZu1^(=-ek6KEyoXEh~r6@QsQ@wP3Qq+ zq(9yGRxuAy@_^LK9OOQw<8ux_> zi_&Qh?Jx@PYuN+Z{=O1R5jstlgyV6tQjz3eOO=Ait|5R>Etc6Fv*E1KVPA7E_P!B@ zRyp9gI2M-net)`T%vHHa2tCtQ7!E*l-ZCQ8Unb@g4#|r^y18;$jXW}m0RGa{;+{jk<;Q;j@yopf8dkqSlPu9;NscJ zj~x8LG6~qvSkz8ILSI{K{Mbk$TvlE27uN&xEKbussqM#l^X4Iz9$=1A9gVJl1kCbC z=NR&mzAB^e;uq@x48u*}2+Z=czL=FJ*P*BmZ{n5XFk9#>F;4dLvk{XOxxI%4^S02 zlAdf-W#r6A<%=8DgwX$f!|qQmJU3FgtDPaBz!9`hAU5lTs+Gn>AKluJ&KgwpK~Y1_ z(RF`^M+ZV%o>I_OKFVF3@>pdyFan9|_R3<8)n)ar!jZg;D>u;1uf@V#RW^8ESsOh8 zKt2K$QF(Wlrt(~Fx(x;y-siX@&a`B)qG zjz>RlSg;y5xixqmR|zC`8&hd2@$0^x73f(*Z^JvS?Q9_1u4>k0yWReAU6G`k;a0L_ zyIj9)^_T#08roa#wMCR?TVy zrSEkX$+uN`Webkm6Ba%Y2qWrUkz>`~ygt{*VSMT=7}C<0H2^VT%~j#ROiejCwC_;y z>sfvD^PTBNTaln<@M$3zM7m{qAHwH`)3f}D)J=~;uvP)3_ z5Sv7hw6;a{cS6=#i-(IIo%- zQ=g0nCgl$?YX4HdW;V@^=kK!iS~m9R zy(uf1lFcwD>GY+@vc}NmJLUka*93iO<(~nPCTv&0t!*m<9Lt>004lZ1yfAJ zmFh)jbFd7ki20pp@fiyRo!@nB;P2Cm{JXWRJbV_0x4XNx70@K3#_idUTzql>zG zkwSwthu((u{V&B73@@xFMu|U5KFx6B-g*30j>qigRU8WAcVBWs!Y>*;dUTOwEnQ`+ zjpLI|A|C82~4DyTJ1KyYh4 zuT%eUNDWt+xfP{v%jTxnILhd9-xrc29q`jbAfS$?2}3|ktsoO)csu|c&tcHw9FSG9 zqHXMNC5Ef_jz^2(2hKx? zjhtW(OH3%|#HjTx9|-eA;tl=5nSHwsU2n|=%9$!B4rD2zt3;uuC$ zjJTSmKOZjDJ1&Z9#sGDx;}{9@GhZ1N7UfC$q-;)+4|&oym7apQU9Mf7CXUWZ?p5xz$GA&Oz!FMG`_D78I9SyHdT6FxoS)xS67+IDxn zP|bH16@gLqBI>2boYl{)&?F}lPTBNXZD(f-7%mae z3=fg@!MuNphWJ%Q8Z3j<@np=RK3JH5l(&)YqEOe*z9c zegG2+m;at|c=qFRB_h3df<#3#|0zs%MS5U#uHZQ%4za3~Z(^7OuUTUv$n-{Lc$iZa zFnR`HRxFY^A<=BT=N*wn{_&1w1+Eji2WlG26WTk|q)eGWfJ{9udW^p;>3$QjI` z*6LZwXC2lcXL0jPT6dK`MT60>{H3%j3SRt4$;TAdQokR&*O%60_%D!lIZM9tv~a5# zFH5Iq<8U)i9ti0$5sX$swS+P7nT~u6TMnBj09g}Xv>;%Oen6mYt&g3jN#HmeH^stG zC8IXU%)5I8?KAkS24-JfO6Z9=lpWJnX2YlDlu@UmyX_*$Nkss(d&kD>>FZ|N3*EM9 zFN+#hf{v?sQ3^eD7|C0-wSj1`jV;lbxM(lL(!hsx9bZ)b9TAOOqWst&1pN{TNv?!^ zAJZ@w>G?F*&yau%;$f0EzK+8$aMKn9fLq?hLccs$1>l>&+2D?52apXYn1?ggNvGi} zj?Wq(*IiY3MA@B^3pVg2HYLPfwJv%iRYvLKxdGT1C%@?z8AGkQNvx}VHF=ESQSwMZ zG|+cwo_ZCe!Fk{yW0n=dFlLSIJ$l7ompOF*5uY0;d+ep|=nzk511-g!y<66yuG66e z`_?pwB&}+NyG5#}9tUz2&)fU#TLv0<$eq_VtRrEAw%lWuN02zY9=elcABF^p*mLqj zMocWyqg~3s`6faoaFtte03hriV_wZ_&V~xGs~nEFBGrQWR;s>28Pj!Py1)JwBmQ4@ z_J2+q5!nX6aGjWV;iglqWO0aF8u*5Y%a+!)?^wqwTA*FI~tM}J59y4Ji_o1`|p zTA9RG%0B5>uelQXVyFLO(wQGbq@Y*3XjWD}qTRs-sDk#OR^ZmvqraPtRMm6vF1Y(A!PN=vwYrERmYB2*fAhY1Z>xQoj z*?jX;gr#RKvL+^+lem&cnl0 z_?5Cu+4o4gP5%@k-?lM*CTDXcQyScbL2}mo|CQmF@+v-{|A9U@@vZP?7x8z!skQ4c zB{I2n9^Rr-@LRhiSG+A_t}hm8)rL8ghpe`JbyM%-dV(9?tXR=nV65e9H@vI~VM*pl z^3N_$#Mi44#EeA+t`aE9!Pt-a?c*^%W7b=5Rm>^HV5qqL7Kw@R z9%D(k5cui)GF|d*(GwK2aU8??zqtGhP)5kNCF1mwp9eumW;Vo2H;}TpxE0z6Z$AD7u zU$+N(4TB6ST%BRoGK12I5m654!3*F%B{{oiT=`m0aCNAQtY>T9-ysuXhxQ8S7c=ZI za_dRdHuiV7g9^b7M;2ku46Q8>CaEr0_&BkVWRVinaawfO(>m>FfQ*V{UZ-K~6=^+S z=o7tTXCkWjyTt@(N72*IAeF(fC5fO-WA@%Ech5AkBYGY^5RYrIA;<8~@UtPUVTu*a z;g3ALQPGTg#2>cSK=y26t1K9IO0TebjrQVuQ*WaL+B(vupO790A$Z}P24T0vTuE-P zdie(?`h9XvpL0RazhMFyeC3~*8H?$4`hTxcXs|bZGWzVyM8^czRPx@?ZX7NwW zz)Zbl9+MDDAz?(9fBeqibrA9+qxhhAO*~+Q8{cK~pGMCA#Y-cp(uaT5SbtdeCod;7 zDDUcZAQjx$PF4FiQ#(V z5?B{a7BUeUNy|s1+2f+acwC&5`<4#uB;9Gq-rKHD{y}G0t@-OLt55ouzHes zB5?fqllddQwjDg)OVkc|2i|{i6^C^48vHUoS^B8nJoZzU?fJZE+N4v|#8x|KeXds+ z03@NIqpTXqozd-&vNywp_%)EodagDy_D%ll+j4C)xQta>LpqnRReOPg(CACj2q=^BL2(xQ-w_zhTXa_2b73_pPcm-R?D&td z!)F&(Meo}#3j?(q##q^pvvj}uH?OoG)X#*epd}Ps$YVnFpRe3<2?oOgZkN+cfOK1EE!tAI&UaIF6iXYicYG<7%uESBOPe1Km z2b^5zA{&|b!kr#hZXkkkT!FVE(O0f3R~4SJBxYWgmqB=buY6`}v-qz&)&m5i63k}y z#4m>*KfCqn@;#|>V(3kUe{jCbj%I{Lw_f@`bJ&SZcl~lp8Jz#hi+)bka31bJXW8hlNB`*2FGOF|)(u|I{lc>8 zWqqo*0GWgSg^(*a2{0cyLDXwG9#m}LahdCepL-YGMPo@0re5vsu=b+e_$GiT2O<1x zD7bVKci!RLxG|-M6L@E=HK8v>Uc{QrhYo>pY|qn61Qk)JemBvEaGrX=%{hchkH(3Ak>}yKdVN|F1GF&%kTXJafd1Img3whb!W0P96zz41aWN-bz0C zkp1_;b$=hsb>G89=!Al2b&E7;!({{Eje*8({l2i;Y${XtN!k7rWI$2=E1KW`u)OqX zW6_2l`E{RK5TQVpZwv?oT-f}{D|EO z7>2mxIotRRK;XWw3g)g>TZs=x$StKpAt%#)hU+F?>ES;EzTHLblTx|!o7VBDi1#7J z(6?ZAs}0B?DYU9%O0If^z7&!1+9viaM3%+^fzKyazH|2?$^Yb40XNXkcv3-!=2{+( zgEtWG5-9NyQ*D_eyouS5;P0FLmb`IlQcH1rqdR{9I{Y=R?!*#*b+BWmofLKXlr>r? zep%6B)V|FSwzK{izzRa9QIp*l^}?z%-%=LGo*=g@4gG~9n$a&Bu3m-KKfiv zOCqMj$xP-is zEYN(pxqQ=J=kpEJTK04*q+0nDpN&CLsambz3ro8Obphd`Mt5I?o|>5eJa^QXrA{q6 zqz>7b(0ew?%0%Inj!`F%)O71;6-n`}8b~WFdiDtla^-_L6ldMyUmoc^(aK7BBPv?e zlqx;G;5MVsQm4qWNXjL$2iD`A51UbGdn#tUKV#J7E;kMfj$slCoF-zjuRNNGr79iz zt|T|PawopElaTL65?cmRVc~3VTvB(nnrV=4=C!__(t(CA-dr&gAvs0d7;XrS0H9Y` zW2BJ`1tlFuG&9~3K%N0rn_hXsU+MU z+{Gay_#KH-(XiT2lwi<;`+MNV(*p*;XkoE~Z;ckU{O9LX2!2t<(dPm+TnXm#75L8< zl*voXG+1pI;*J{GGW#oIioFP^;u2Du3=Z3ga@s==TMeaI& zDI!3zhL;_cw3sjFK-yQ45lL#~l+Ube?h0@Y)^s$q+I6e3mCwB9GBNM!N@PP`Z;;0tCObg3&VB=Fw@pS|4lcy!^KU7hS6GP@F z4O%u5pn@Ybkey`SBkTT{x!+keC({XoTCol^sp6C;$1~X^+5E!*+u8DLdrAQLQ(u;P zzVJij`ttxXpamV3pgbup0ovDMj5ce&OekPY0bB}O3>iAVWcKTk4U34#iFdeZC2;t& z%;(H$zJOChJ&LgIW=qKM7hpp$yA=&Kg84p?cZpYe`-v8VK z{12cAVYQT9E%3Pm6Diy7hOG1~mqb8BwR)YdvkQo)^jpZfnO`RvjjTN(%?3eK=*7T0 zQKCAB^%vrGAEZJ*TVu_Vbi}_4c8Dooe=+BDQW8!&LPpJ2rX#j}vm|7F(>ghC`--uo z)iRc=zZ%IwqRfWgF{0;d7hjmWQt&np!8@m+1gnnDN5#@r~_8&-fCX{;+mXDB;n_Yfq}(L{`+W z#_Dq*HKil;|iA5!x9rUbuCMGB5)en{nt zL$$vl%s?H?WixT2TBA02-Y1NpkZ$0uCY4Cz({d0w1?K1_;T+>eEoka#GBI(cK}5mFMDZhFAy_DK z(cr$SBqRZQjMpvGBbe8{r9+c*eW^Uew(1EcWYILeKqQXL&5Y}~LkgOe!=X99O}~K{ z(MQcy8GlYw_OQN2TFDtcapCI!F)ak&bGrQ-epO>?PTOb+Uq`zP1*u|}E!MxecUVj) zOuBmsT69Qwk6?v@|0unN5`$V4Kfayzs)~R`qlEFy6|FlqQDl;`dzNDOekN=#?*)_NL^b;6lL#;o%ae_m{4-OIf5XS@+s90pj(nqr^A?xFbdO`i7 zOhG3j8r@ESUgqKryv)k1u}p7s`%@5+f`{|;B_P(9qZfyl3n%=TD(DP(s*G4WHCkYX zTNneSO0R5j{<%i~Q=&!>Mp~U9cBL4XQF%%nj+cI7IHtRMcPAU_761#YFg5nYD;Q(F z3u_Xou$x$2*xRzPmhyrwA@b?0#&F9F>W$ZD!cZ3%b?~ayl6i2yN<+w1Td4A@aUL0> z7;K(ES$(Kna4k+6N8-Q&v>txwn7reEvQ$%<$wO`%N+Q3&1z&(VL@}|(=GEE`Shi@y z*ndV$t@P`pxwr%#9#{`S!mb2H{I8D5(2jJxBmm>4qZBj;WXtV(jAgy?4pGZ}{AlJSbuuHF9NW|; zf`lBvv%~|>{yD?`ry=Xii?Y9Y3nL$uNqx*A^KEk&esBuAO@fcN_vYa%X0*wGFahFm zk?z6f=`NTVD=5Bi9zFk+E=M%r3fo%$U=(HVyMw*%D06 z_zO9guYxrffHOE;ns~>oY`{J`C%0e+ZU~>j1mU8KLGc^u1t7y$gu|;~7m+fk#Hcgn zS)^-+ClWq0^5R(r39QvMi^V^ty1lZZ%llpH*R1qb|M}pzeFsrc4t=QRYH?9aFfrLTHp!|bs99(1aVf^i_ZpU9v*DV zd~0sUIpEH&3B_az0T02#>I!A{_ z^j7v~MgV5XmcEG*CP^>pQe4HkWNOLy8&j1h-MTb)n7!>)jfbE>1yuKTdm+KUU+LOW5uW0 zboj$-A$cUu2WFi=3LB5(Tw1Q)n88$-lMs8A2(%kd!()j^GhBS()73m`-=}kYb39ym zVP&mh&hym47FzbulDkERI;M2o6triU;r2_1Hl}RVpW_6YLhHhDJfQ01~i+6=cpBeKn8*38NQjBit}0F7hx{n%RVOSr{wvd$S@mP&|JQBfeHm6Zp?WNVWvnWPKVuXDJM&05@z>GyV zuV`VJSe=_48QZto^VPZS=xpnL`pcQ63lnfvbKze!g6~0D1(`k+X)Q2&D|ijiSNtM^ zv^V3{_q4kvPCV{^NjTv~+SW3U+vrk+94#AR(yy>%Rjt}Gk7*g5#uJotOV4m;@O83$ zQu9USiqus5Py;&E6Z}}C*bA$ZD<;(0ITIKfbn6*r#bPKfiFFe8?0s zwn$1TGAp>^6ihe|AJX#nTc%=Ts{X=qXc`|WZab8yEB=ZFZv>!DdrNGpv=2{c(Y9fo zvjDGuP3U`n734kooVGd|JScSijonmw_Fa2b5+)n$Dces!k|KrTL|I|8jA^r)%-TR6 zwla-lkHvI#^w; zCaXtHvs%!Yq0_m{NGp@DPl2WmCIhxVFc>${icSh$)#4n_jWD7>o3wO4hu7-pklVty15mvp$T|GZgPu9Kzr)_y2V_^py{|I5Pfwnc+4JbQF0 zN5vdVX1j#PAKqAd+LRfr)y5k}F#HM+dqA4w9+rOF>^rXkMAzwEu(CIsinE@>6}Wmm zGyKFPAGR!3F_gM4l-Bm-pk}UC45{Ld$MpdwFJW;@t(vf%cPuUgbe`%x2Im0@R}KP~ z-TZt2;(x`E{m(e!MpC@pOzs+IUyF(R!J&p(`ULqN_vV|*) z{I}sgkhwnJ@oh^Co~WP?jIzFUxt4xkVpsWe<(>V&FV8n#fT3i0QGheAPQZriBTr7dgOS%S<6*)@Bq}0yf`%Yvvcsk! z!a$)>(-VW|lTcv$m+I)NKA+%oAsZ$T8e06J+CE44y?59Q#)gsqXjUYAqwT^io~JN^ zD_T{hICi4{W6#x6y&VWhy0`;)FXK{M)qJ+tSEewnWANHyX!_{@FGbQEpl00)4s-U) zBnYQTajM?2+8K_6jTgzHu3T>(=O_S7nSY&$_*FdMv^S7{6mx2h*pGH^8c^3=SuGN#ruM~5=L z|C(f0xi!Hp<0+W)qtkLRp5|%hIb7rhYASg)WmOJVEd6RFKAelq^F8EHT115*Hf*0< z$`6g8Z)-8%hF9#Rgr>4qBD|)y4f;w{v$;55T!{Uiul%oVuwu#s8Y>z=LaMB&LBdtOwci zmeMLhYF`6vu5*zkpsNTPgGm(EijkYVUn8TIt??so-snn;)ay$5!H`WmGP+{lUt@?P4lW(#4a;FT=0X7q?C-e6S|qmuRtLV;;1GB zJYHe=vcz5lkK8ZG3WW?&9=8Y&MInbMy%GG1aio<|;1AXd=!(;n@ai}f$uuLBsVRReqxG$s({TD{>OnW*e0nrwFoO?Y zeLz(e$(M)xJ;Ao zP&JWb$=69g19^(}t_F{OS51>LtqXF{@C#^}$5iT~P1t*$=sX#AA|s9YvW92tIEKr| zNu_sS?a*a(&N%SL=<6e^65^vk`}TkUt&WSZE|g%4*dcmcYdbwYhcoXb?m`~VT0+E` z%~*!~n>3hVf1(cy^xW+(v(r9RUy0u>VoFSgGV1C*si{qahd>$T#ks>Ey~MS3^BBi! z9U_mCX4t@yd$Pdfs8J9yCAP!qN9*9FFu#Y$#39utBzE4kk+$yg^hiSqr&Wz9O&XQvvQh zB{Dh@Q+Bx99!)I8dP&Xg`n?cmDjO0*=kagvwuThaI`$ z!r3^wAzqH8SWpWoqLN{)>F!{>wU2%O%T$K>*?S`Pt)zc_RKG>Z(03 z6t_%|Sy@AbV?ONTXLCq}l-4-$Z^%SP^9R(#@Mcx?p{h^3uASX_VN=Rouk$8dgR^N< zj-5zml(Hc`{Zt#E`SNARbkHy5QethXy!VvLVQA4QZY*$Dai!LreY$!$aIrP+u)wYy zX>|toE011aDhiucx%$l0j!qMFffWPxG9 z(;{FLE91e7{6nsZZ;~O!M9ZY_Q6AYyRhXBbpTGOfz9LdC?|e)OS!pvDh?tv{m%N)qR^r@goN3%5ZHN(L~Td% z#b93xAMVz60uhghRhxtdhbvb{276DTqNFN68^%UHUEEa61Nu$Sd}UHpg6hF#c9sdt zoQ&*Wv3uG`Mj&E^V^P`QWk2vXeGgeu!N)cWe0J@`HnEDGr0U9RP2(wNsmKxBmJa)h)+ShvXg7d> z97fOTAA4<@@#cn+>T_YY%C%Q(hWy#Et3UCcHDyIb_k@`qj@AF!@3;T9#HN2B<*Fm2{eH!fbuqT>kjL=8{FxRd zw)ebfVTPwn_Sposhu*K>NT0Y;jFGi83Gj?6TU5Rq+aZgNWTA)MjShbIA~m?f7H1q8 zf<`7zTG^@kP>WJQ6dAXZCa2E{&{00 zG?-=sH9Y}5OM;EHAO2Wz;E?MuhXz^w=s5z&WM6bac1>ORJQKa};Q7%RB*fLn$FSin z_Lb$y+77CMTm;LHt7r2z&x+HwWbFel+`{rt* zgSiR%12tsUH*t|Yd*Qk9MUeU-d0>C5+F#%Fqwbmb-&;tyZw_7+vtMe;z-RlK0_{ zr>Agbv-9}%z-+{!t$gQ&XeUAYWj+^2+Y>kQ&U@8W0_2mf2L_CIa{9;Tj1KQ$65msl zg`Mza-;VZ(o_54t`D)JY$-YFA=HAZk9VTU~Z@Qk@c}#fnE_~PLQuboQpd-t#uW^&= zG=uTu?`60qSCU$7E*10~CCW3Oy+ul!1{KdLlL2Wx=OMbBta^-9o;mF4e0gor4^>M4 zUwh~I)zr4GaXTsqvgt*dAP5Qq5=05HY(%<>f*2tvU4eigMN*W0=o;9dv@ME=^rCcv zQi2JDreHvb5UK%U2#|z?#@31rt3a8@UZoeb!@uG^NxsJBxAAR6G%5B@k?3fn&m78 zK(Dq#a4E?)|9D<=w`*hDpS43MqiTLTunKJMkiS)*tS15OChyKP0xR`U06IlxoCbs`X%~+t48?jcc&*J3yofpN-$?G zP@l90^RVi1qFug6e|~9x!-KvhP4J@6VnaZoArNdRvV5{N9`W zL9i22khv#uIb-(|FP(sf+m%VKq#6Zc^}n1*^vs4`;+9|2DWq=ecj&0ju{zf4vB7ve z8VmFKjtnFExxB|Edlm$t9)dBR$5V~|FYPx@p#3J^XuqsT{9a)oOM})L5H%j8?hR_s z7c_gCPgm|>N4_UN>Fz%xVAKPGQcreSa5?P&Y^;$~5-`FXCkBh@;P~oe0_x zlTU{R!*)@mkQ(a22ror>?ZRFF543DFX^(-|-VXXa`0Vez*;wQ(O!HnuJ$rb-Uwzr= zMy#xmv}Wm0cWjN&yFU=kK%o8ZxXahmx5@{USBfPvLMi(^Uc|lKbx|~+3VNyAuX2ej z8>&%J`g+V_Gz}bPetoZ$iC4O`PR->s$CIHgUYfv?3c{o89hbxVY%~sr^z{lknQvPS z!{pxE96R77J;g2bARde+rj~!xH*t3Kv?M~Vpm;EHU5ePZ#(jM*mv9+(hTrA=nR&4{ zL5SGV@JU-e58je2B3~gbUU#=KP1Za;Q+=-N#|LV0)eAKdWzu-gy%s(1C_1<&iJBt(aQz}Cg7}7t*-zrVhFpm?pG9wJju)6<4 z{XX^Tk#mNw-kw^uXR}tBdSEjCh*zYQwi$Kw)A9J_@{Ic~cHTnfw0(w#E}`%iXRDya zuK7P9AcBcp7A{NoEIxR3f9)lQOYKLX4?ehFExY(cRMpUA_BGb5JoAzF1z3uDMdtNq zwgkU#cd*f$SjGnN-F#uaiLg~e%yO{8-6XT-tfAj@($#_HAX~c(@<a=UM*x3K1Tk4lJh;Nof( z%GrEJNmjZ6KF4lo?P*|R;nr!Fh0plPJn;A^kesOF8*1TO9&>9=40mqvb5DK18LwlU zjng)K(9xn<++-XWjrro*Dw#uS>~^;g)P0>)AtswO*f=XIMvKd(*)%@8o;%&5Zap$! ze3M+(XO&{1duuzxNFim8P`2LP`eEIR;kkXME2Nv%Qgi2qsK{-byrX(Doc%2pK6uju zp?$F~mx-~ep%}@4&EYEYK()7fWWGdhZ7f!2zI(37^M>VioJJKn@iUELBnpB*G>Q&d zj~irs?~h%SsO%ev$P{05jwZemctUwMvFwv@%*U^INqWFO%JMD0{GX;M$|6RXdt_s_pMJeEP0qqMw>3w5TCE zR}WeW&1SHd>UAMpE=xE7MBOsyb615uRjsNsyH$6a-&PPlJ*p7V6_&G#f0beAW&M72 zr>DP?vgf4LY)SyT-eEuBe(#|;6sdok_+a3)KSs)|so8gc7c-=JNWGgd`*7>{IIRu_ z(@>X-OnO$6LjOm=Wz_QW=YFx&em}Q^I#f4-)T)D`7|Yxb!usPsVi;Hr-=X{T#@$gb5fG zLiK@~;;9Mq(_0BeC#;g^O#7+c2I%U1qXQL{UooH*VXJDF_vn>_%{%%kJ0uMDJ_T1q0f0ER%?NqV-78-ms9zphm zofzkuYe5at*P(UbZdC5c%qyuygAmUbou0i{iH}Sd$6tr!o^Lw#ws2RfJAS`%Ql8sx z5q#Sh8y5^k<$0#oVTT^MUGq>(-Mu@dc3x1x4X#ou+f>u=fEV!TLJ0I4NMFk zyWtswOnK4S7YD%Dk#}lI_Dip*B66u-W-6wC?wA2N{$bKuyJ}c!5%cP6x5$8V)!W}+ z9y5+Fb1vbw`+pq`Z)0xP_w*(UVdTI$);0cI@X3sfIN{UeXG`}szn|ZczBNJ8s{Lq> z5dRXpioZn0K4dlG{KmT9YS0l+BYT$J>?}#wu03a<_K)A?72HpDKJ;_(DcB&VB zkwxu>uPoZou zGr!=58rOZq-S7P70%XGMq{P@D3QLuSnoE;Ya&ylv<%(I{@l$qz3>4%Z#rf1v;$LGy z{s{#(QMfGC?i4BCtKQ0;`wd)f8PdBQPfaEUxz=8Z-m(P2G85AuT*9REwt&=R^&~Eg z9Hhu5H!`Y`kF8WPm*s@_jpy~II89Srv9|K%H^T$2$Q=O#fbsE`I#Xi;f$*+6U(Vee zDJ4p5?oQP6UCv&DXXok@YwmpNaQ9v+EYK99BI`@55;zC`y#nmi66Np6CHgr+Ct$h5 zzU+~xQn=q3_PICZe(OP?P;02(jQgu?Mrm7@YV-${%_7)l(KLD-AVw;ubePGF z`mYV&|2RmDs&88_9~5|U(vywpca%DD+ng=Ob2<(?Yi1r8+o`U*OjCy;D zDA3n#i$&g814zs(ff4z7%zsI5NwZEc^$L}AO7t%o&-_6Wf`|UTQ~3m8-efLvrBSnKW9*)A@WQ{fy7F=w{mEW^2oU)< zNaOQp762u}djGv>J;W3cvZT!W`AeBw5%3TEW+}@2aQbCNi9J$#bHM^4Z+ir@fJ1X} zFPE2rNLN=NQpp0qS_r*lw$hMYn-PsI-li0}K@#2@!XYixqp0joKU@L4XzlO?}o~Ol||2J7-+*g^~_qvXOfH3k|6esU~O^hl=G< zr%|f_+x$)V8^k0dp9wbIui9pNUqN;-0JRX0(u(9U3eYShD92Oae+}(8VC>;PMLa|6 zg#~#i1K#W;8bEn)t{f!@Q1`%>E7C)j+?WUQhVVoX$k@162)uBPB#%`pnx_b)TC{H=I0aO(tdHC>?sF+*&W{WBht+ZB{M@sL_hrW`(a$82p+)oe13 zB2Zlrs=igAK;9Pc%@W5xZf-uLV*wt#9UaTgo`LT+(G^4ejDAVb!*a_Mv$*~Xz?CI~ zTm0qY0iZV|QHha_`yIl@K%8TCnXW!bOP-{shAHY|*>gEB$KlwCO0pT;cs(}&KZ)Q5uM4j=IkI!md@&s@y z7wPHb#gN3v4nZE+`N>{i!l`s%K=8?2F`*#<2nM?aD8ZYh)3z4l z4YPkh3kA2({M*(1ZJ!-iAv;#EcobNGe_ZKONaDis7oG^U%oi}~75!!7O`932Vi5`KWxP&hGC+2ROBRTw zuKQu_P*XCf7vaLJGH-BcPZZCI{{tpvc{XAMk<A{NkOHQWI zPUm#r_VzALfF5QmC$PaUY$<2wOy;;~2IT1~L-yd?z7abrJicgNHTP9&N}^W;wf>ZuSq{^yhIsJ@3-eQc`d}BX|14)r6N;xqK{#bNp9P7{i+Uw?AFzUq_(jGVf literal 0 HcmV?d00001 diff --git a/figures/CW44_naive_pre_crop_img.png b/figures/CW44_naive_pre_crop_img.png new file mode 100644 index 0000000000000000000000000000000000000000..59b92143edf0dde5ed22f254cbf18016b54e7698 GIT binary patch literal 62126 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�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 Di%W(< literal 0 HcmV?d00001 diff --git a/notes/preprocessing.md b/notes/preprocessing.md new file mode 100644 index 0000000..c42d086 --- /dev/null +++ b/notes/preprocessing.md @@ -0,0 +1,13 @@ +# Preprocessing description + +## Default cropping + +[precrop](../figures/CW44_naive_pre_crop_img.png) + +[crop](../figures/CW44_naive_default_crop_img.png) + +## Antialiasing + +Differences greater than 1e-3. Due to aliasing I'm pretty sure. + +[alias](../figures/preprocessing_difference_alias.png) From 7a58426d94cfb84ae673511c315c8afef5d20f6f Mon Sep 17 00:00:00 2001 From: Taiga Abe Date: Tue, 4 Mar 2025 12:38:49 -0800 Subject: [PATCH 02/34] added and tested pytorch data module --- src/behavioral_autoencoder/dataloading.py | 140 ++++++++++++++++++++++ tests/test_dataloading.py | 126 +++++++++++++++++++ 2 files changed, 266 insertions(+) create mode 100644 src/behavioral_autoencoder/dataloading.py create mode 100644 tests/test_dataloading.py diff --git a/src/behavioral_autoencoder/dataloading.py b/src/behavioral_autoencoder/dataloading.py new file mode 100644 index 0000000..e680e97 --- /dev/null +++ b/src/behavioral_autoencoder/dataloading.py @@ -0,0 +1,140 @@ +import torchvision +import numpy as np +import torch +from torch.utils.data import Subset,DataLoader +from behavioral_autoencoder.dataset import SessionFramesTorchvision,CropResizeProportion +import pytorch_lightning as pl +from joblib import Memory +import os + +def calculate_mean_image(data_path,dataset_config,subsample_rate,subsample_offset): + """ + Calculate mean image from given training set parameters. Gets parameters from SessionFramesDataModule + TODO: implement caching for this function. + """ + # 1. First construct the right training set indices: + dataset = SessionFramesTorchvision(data_path,**dataset_config) + all_indices = np.arange(len(dataset)) + ## Subsample indices + train_inds = all_indices[subsample_offset::subsample_rate] + trainset = Subset(dataset,train_inds) + + ## use dataloader to compute sum image + trainloader = DataLoader(trainset,batch_size=10) + + sum_im = torch.zeros(dataset[0].shape) + for data in trainloader: + sum_im+=data.sum(axis=0) + mean=sum_im/len(trainset) + return mean + +class SessionFramesDataModule(pl.LightningDataModule): + """Lightning data module collects together data loading logic frame subsampling, and then calculates the framewise mean of the training dataset. + This datamodule does the following: + 1. Subsamples at a given subsetting rate to generate the training data. + + 2. Checks if we have already calculated the mean image for the given training parameters + 3. Generates the training dataset by subsampling at the given subsampling rate and offset. Calculates mean image if does not exist yet. + 4. Defines datasets which compose given transformations with a subtraction of the training set mean. + + """ + def __init__( + self, + dataset_config, + batch_size, + num_workers, + trainset_subsample_rate, + trainset_subsample_offset, + trainset_subtract_mean=True, + ): + """ + By default, checks if we have already calculated the image mean on a particular dataset for a particular training set, and if so gets that cached mean. + + Parameters + ---------- + dataset_config : dict + dictionary containing configuration parameters for dataset. Must include: + data_path: str + path of the top level folder of per-trial frames. + transform : any + the transform function that we will apply to all the data. Can be followed by different training, testing transforms for different datasets, and can be None. + Can optionally also include: + extension : str + extension of files which + trial_pattern : str + regex which filters for certain folder prefixes in the trial. + batch_size : int + frames per batch + trainset_subsample_rate : int + take one frame for every `trainset_subsample_rate` frames. + trainset_subsample_offset : int + offset for subsampling. + trainset_subtract_mean : bool + whether we should calculate and subtract the mean of the training set. + """ + super().__init__() + self.data_path = dataset_config.pop("data_path") + self.dataset_config = dataset_config + self.batch_size = batch_size + self.num_workers = num_workers + self.subsample_rate = trainset_subsample_rate + self.subsample_offset = trainset_subsample_offset + self.subtract_mean = trainset_subtract_mean + + if self.subtract_mean: + self.mean_image = calculate_mean_image(self.data_path,dataset_config,self.subsample_rate,self.subsample_offset) + subtract_mean = torchvision.transforms.Lambda(self.subtract_mean_image) + # augment the transformation for our datasets: + if self.dataset_config["transform"] is not None: + mean_normed_transform = torchvision.transforms.Compose([ + self.dataset_config["transform"], + subtract_mean + ]) + else: + mean_normed_transform = torchvision.transforms.Compose([ + subtract_mean + ]) + self.transform = mean_normed_transform + else: + self.transform = self.dataset_config["transform"] + + self.setup() + + def subtract_mean_image(self,x): + return x-self.mean_image + + def setup(self): + _ = self.dataset_config.pop("transform") + dataset = SessionFramesTorchvision(self.data_path,transform = self.transform,**self.dataset_config) + all_indices = np.arange(len(dataset)) + ## Subsample indices + train_inds = all_indices[self.subsample_offset::self.subsample_rate] + test_inds = [i for i in all_indices if not (i in train_inds)] + self.trainset = Subset(dataset,train_inds) + self.valset = Subset(dataset,test_inds) + + def train_dataloader(self,shuffle=True): + dataloader = DataLoader( + self.trainset, + batch_size=self.batch_size, + num_workers=self.num_workers, + shuffle=shuffle, + drop_last =False, + pin_memory=True + ) + return dataloader + + def val_dataloader(self): + dataloader = DataLoader( + self.valset, + batch_size=self.batch_size, + num_workers=self.num_workers, + shuffle=False, + drop_last =False, + pin_memory=True + ) + return dataloader + + def test_dataloader(self): + return self.val_dataloader() + diff --git a/tests/test_dataloading.py b/tests/test_dataloading.py new file mode 100644 index 0000000..83224a6 --- /dev/null +++ b/tests/test_dataloading.py @@ -0,0 +1,126 @@ +""" +Test what the dataloader does. +""" +from behavioral_autoencoder.dataset import CropResizeProportion +import json +import os +import cv2 +import numpy as np +from behavioral_autoencoder.dataloading import SessionFramesDataModule + + +here = os.path.abspath(os.path.dirname(__file__)) + +def temp_hierarchical_folder_generator(tmp_path, n_trials=3, n_ims_per_trial=10, extra_files=None): + """Creates a temporary hierarchical folder structure for testing. + + This fixture creates a folder structure that mimics a session with multiple trials, + where each trial contains randomly sampled images. The structure is: + temp_session/ + ├── 0_trial/ + │ ├── frame_000000.png + │ ├── frame_000001.png + │ ├── ... + │ └── extra_file.txt + ├── 1_trial/ + │ ├── frame_000000.png + │ ├── ... + │ └── extra_file.txt + └── ... + + Parameters + ---------- + tmp_path : Path + Pytest fixture providing temporary directory path + n_trials : int, optional + Number of trial folders to create, by default 3 + n_ims_per_trial : int, optional + Number of images to create per trial, by default 10 + extra_files : list of str, optional + List of extra file names to create in each trial directory + + Returns + ------- + Path + Path to the created temporary session directory + """ + # Set random seed for reproducibility + np.random.seed(42) + + # Load example images + example_images = np.load('./test_data/example_images.npy')*255 + + # Create session directory + session_dir = tmp_path / "temp_session" + session_dir.mkdir() + + # Create trial folders and populate with images + for trial in range(n_trials): + trial_dir = session_dir / f"{trial}_trial" + trial_dir.mkdir() + + # Randomly sample images + selected_images = example_images[ + np.random.choice(len(example_images), n_ims_per_trial, replace=True) + ] + + # Save images as PNGs + for i, img in enumerate(selected_images): + img_path = trial_dir / f"frame_{i:06d}.png" + cv2.imwrite(str(img_path), img) + + # Create extra files within each trial folder if specified + if extra_files: + for filename in extra_files: + (trial_dir / filename).touch() + + return session_dir + +class Test_SessionFramesDataModule(): + config_path = os.path.join(here,"..","configs","crop_configs","alm_side.json") + def test_init(self,tmp_path): + with open(self.config_path,"r") as f: + crop_config = json.load(f) + # Create a larger dataset + n_trials = 10 # Increased number of trials + n_ims_per_trial = 50 # Increased images per trial + session_dir = temp_hierarchical_folder_generator( + tmp_path, + n_trials=n_trials, + n_ims_per_trial=n_ims_per_trial + ) + + alm_cropping = CropResizeProportion(self.config_path) + data_config = { + "data_path":session_dir, + "transform":alm_cropping, + "extension":".png", + "trial_pattern":None + } + sfdm = SessionFramesDataModule(data_config,10,2,10,1) + assert sfdm.mean_image.shape == (crop_config["target_h"],crop_config["target_w"]) + + def test_train_dataloader(self,tmp_path): + with open(self.config_path,"r") as f: + crop_config = json.load(f) + # Create a larger dataset + n_trials = 10 # Increased number of trials + n_ims_per_trial = 50 # Increased images per trial + session_dir = temp_hierarchical_folder_generator( + tmp_path, + n_trials=n_trials, + n_ims_per_trial=n_ims_per_trial + ) + + alm_cropping = CropResizeProportion(self.config_path) + data_config = { + "data_path":session_dir, + "transform":alm_cropping, + "extension":".png", + "trial_pattern":None + } + sfdm = SessionFramesDataModule(data_config,10,2,10,1,) + dataloader = sfdm.train_dataloader() + for batch in dataloader: + assert batch.shape[1:] == (crop_config["target_h"],crop_config["target_w"]) + From 5d14ac58b19dcf293feb8ef7d39539c9d4c00489 Mon Sep 17 00:00:00 2001 From: Taiga Abe Date: Tue, 4 Mar 2025 17:23:29 -0800 Subject: [PATCH 03/34] added source code for a minimal implementation of single session AE. --- src/behavioral_autoencoder/README.md | 7 + src/behavioral_autoencoder/dataloading.py | 8 +- src/behavioral_autoencoder/dataset.py | 9 +- src/behavioral_autoencoder/metrics.py | 13 ++ src/behavioral_autoencoder/module.py | 85 +++++++++++ src/behavioral_autoencoder/networks.py | 166 ++++++++++++++++++++++ 6 files changed, 281 insertions(+), 7 deletions(-) create mode 100644 src/behavioral_autoencoder/README.md create mode 100644 src/behavioral_autoencoder/metrics.py create mode 100644 src/behavioral_autoencoder/module.py create mode 100644 src/behavioral_autoencoder/networks.py diff --git a/src/behavioral_autoencoder/README.md b/src/behavioral_autoencoder/README.md new file mode 100644 index 0000000..6363eb8 --- /dev/null +++ b/src/behavioral_autoencoder/README.md @@ -0,0 +1,7 @@ +# README +This package is organized as follows: +- `networks.py` contains actual descriptions of network architectures which can be used as components of an autoencoder model. +- `module.py` contains the logic which specifies which combination of network architectures correspond to what kind of models. +- `metrics.py` contains custom build evaluation metrics. +- `dataloading.py` contains code for the dataloaders with preprocessing. +- `dataset.py` contains code to specify how datasets should be structured. diff --git a/src/behavioral_autoencoder/dataloading.py b/src/behavioral_autoencoder/dataloading.py index e680e97..67bab10 100644 --- a/src/behavioral_autoencoder/dataloading.py +++ b/src/behavioral_autoencoder/dataloading.py @@ -22,9 +22,9 @@ def calculate_mean_image(data_path,dataset_config,subsample_rate,subsample_offse ## use dataloader to compute sum image trainloader = DataLoader(trainset,batch_size=10) - sum_im = torch.zeros(dataset[0].shape) + sum_im = torch.zeros(dataset[0].shape[1:]) ## should be for data in trainloader: - sum_im+=data.sum(axis=0) + sum_im+=data.sum(axis=0).sum(axis=0) ## sum across the batch and sequence dimensions. mean=sum_im/len(trainset) return mean @@ -98,12 +98,10 @@ def __init__( else: self.transform = self.dataset_config["transform"] - self.setup() - def subtract_mean_image(self,x): return x-self.mean_image - def setup(self): + def setup(self,stage): _ = self.dataset_config.pop("transform") dataset = SessionFramesTorchvision(self.data_path,transform = self.transform,**self.dataset_config) all_indices = np.arange(len(dataset)) diff --git a/src/behavioral_autoencoder/dataset.py b/src/behavioral_autoencoder/dataset.py index 12f7ac1..cb02b89 100644 --- a/src/behavioral_autoencoder/dataset.py +++ b/src/behavioral_autoencoder/dataset.py @@ -95,6 +95,7 @@ class SessionFramesDataset(Dataset): cumsum_n_trials : arraylike cumulative index for frame index across all trials. + """ def __init__(self, base_folder, extension=".png", crop_info={'h_coord': 26}, trial_pattern=None): @@ -299,9 +300,11 @@ class SessionFramesTorchvision(Dataset): cumulative index for frame index across all trials. transform : any None or transform function + seq_length : int + we end up outputting data which has a sequence dimension for consistency with other implementations as a singleton dimension preceding the others. """ - def __init__(self, base_folder, extension=".png", trial_pattern=None, transform = None): + def __init__(self, base_folder, extension=".png", trial_pattern=None, transform = None, seq_length = 1): """ Parameters ---------- @@ -319,6 +322,8 @@ def __init__(self, base_folder, extension=".png", trial_pattern=None, transform """ self.base_folder = base_folder self.extension = extension + self.seq_length = seq_length + assert seq_length == 1, "can't do more than this. " # Get all items in the base folder all_items = os.listdir(base_folder) @@ -386,6 +391,6 @@ def __getitem__(self, idx, method = "searchsorted"): img = Image.open(image_path) if self.transform: img = self.transform(img) - return img + return img[None,:] diff --git a/src/behavioral_autoencoder/metrics.py b/src/behavioral_autoencoder/metrics.py new file mode 100644 index 0000000..bbb91b2 --- /dev/null +++ b/src/behavioral_autoencoder/metrics.py @@ -0,0 +1,13 @@ +""" +Metrics and losses for network training +""" +import torch + +def L2_loss(batch): + """Given a batch of latents (shaped batch,sequence,activations), calculates the squared norm of the activations across the entire batch and returns shape batch. + + """ + squared_acts = batch**2 + l2_norm = torch.sum(squared_acts,axis=-1) + mean_l2 = torch.mean(l2_norm) + return mean_l2 diff --git a/src/behavioral_autoencoder/module.py b/src/behavioral_autoencoder/module.py new file mode 100644 index 0000000..38a1749 --- /dev/null +++ b/src/behavioral_autoencoder/module.py @@ -0,0 +1,85 @@ +import pytorch_lightning as pl +import torch +import torch.nn as nn +from behavioral_autoencoder.metrics import L2_loss +from behavioral_autoencoder.networks import SingleSessionAutoEncoder +from torch.optim.lr_scheduler import LinearLR + +models = { + "single_session_autoencoder":SingleSessionAutoEncoder + } + +class Autoencoder_Models(pl.LightningModule): + """ + Abstract base class for Autoencoder models. Handles things like loss definition, model choice, + """ + def __init__(self,hparams): + super().__init__() + self.save_hyperparameters(hparams) + self.reconstruct_criterion = nn.MSELoss() + self.shrink_criterion = L2_loss + def forward(self,batch): + images = batch + latents,predictions = self.model(images) + return latents,predictions + def training_step(self, batch, batch_nb): + predictions,latents = self.forward(batch) + rec_loss = self.reconstruct_criterion(predictions,batch) + shrink_loss = self.shrink_criterion(latents) + loss = rec_loss+self.hparams["train_config"]["l2_weight"]*shrink_loss + self.log("loss/train", loss) + self.log("mse/train", rec_loss) + return loss + def validation_step(self, batch, batch_nb): + predictions,latents = self.forward(batch) + rec_loss = self.reconstruct_criterion(predictions,batch) + shrink_loss = self.shrink_criterion(latents) + loss = rec_loss+self.hparams["train_config"]["l2_weight"]*shrink_loss + self.log("loss/val", loss) + self.log("mse/val", rec_loss) + def test_step(self, batch, batch_nb): + predictions,latents = self.forward(batch) + rec_loss = self.reconstruct_criterion(predictions,batch) + shrink_loss = self.shrink_criterion(latents) + loss = rec_loss+self.hparams["train_config"]["l2_weight"]*shrink_loss + self.log("loss/val", loss) + self.log("mse/val", rec_loss) + def configure_optimizers(self): + optimizer = torch.optim.Adam( + self.model.parameters(), + lr=self.hparams["train_config"]["learning_rate"], + weight_decay=self.hparams["train_config"]["weight_decay"], + ) + scheduler = self.setup_scheduler(optimizer) + return [optimizer], [scheduler] + def setup_scheduler(self,optimizer): + """Chooses between the cosine learning rate scheduler, linear scheduler, or step scheduler. + + """ + if self.hparams["train_config"]["scheduler"] == "linear": + scheduler = { + "scheduler": LinearLR( + optimizer,start_factor=self.hparams["train_config"]["start_factor"], + end_factor=self.hparams["train_config"]["end_factor"], + total_iters=self.hparams["train_config"]["warmup_steps"] + ), + "interval": "step", + "name": "learning_rate" + } + elif self.hparams["train_config"]["scheduler"] == "step": + scheduler = { + "scheduler": torch.optim.lr_scheduler.MultiStepLR( + optimizer, milestones = [60,120,160], gamma = 0.1, last_epoch=-1 + ), + "interval": "epoch", + "frequency":1, + "name": "learning_rate", + } + return scheduler + +class SingleSessionModule(Autoencoder_Models): + """Class for single session autoencoder. + """ + def __init__(self,hparams): + super().__init__(hparams) + self.model = models[hparams["model"]](hparams["model_config"]) diff --git a/src/behavioral_autoencoder/networks.py b/src/behavioral_autoencoder/networks.py new file mode 100644 index 0000000..41461dc --- /dev/null +++ b/src/behavioral_autoencoder/networks.py @@ -0,0 +1,166 @@ +""" +Network architectures. +""" +import torch.nn as nn + +class PreConvBlock(nn.Module): + def __init__(self, + in_channels = 1, + out_channels = 16, + kernel = 3, + stride=1, + pool_size = 2, + use_batch_norm = False): + """ + Args: + in_channels (int): Number of input channels. + out_channels (int): Number of output channels. + stride (int): Stride for the convolutional layers. + """ + super(PreConvBlock, self).__init__() + + self.layers = nn.ModuleList([ + nn.Conv2d(in_channels, out_channels, + kernel_size=kernel, stride=stride, + padding=(kernel - stride) // 2, + bias= not use_batch_norm), + ]) + if use_batch_norm: + self.layers.append(nn.BatchNorm2d(out_channels)) + + self.layers.append(nn.ReLU(inplace=True)) + self.layers.append(nn.MaxPool2d(kernel_size=pool_size)) + + def forward(self, x): + for layer in self.layers: + x = layer(x) + return x + +class ResidualBlock(nn.Module): + def __init__(self, + n_channels = 16, + kernel = 3, + stride = 1, + use_batch_norm = False, + downsample=None, + pool_size = 4, + n_layers = 3): + """ + Args: + in_channels (int): Number of input channels. + out_channels (int): Number of output channels. + stride (int): Stride for the convolutional layers. + downsample (nn.Module, optional): Downsampling layer if input and output dimensions differ. + """ + super(ResidualBlock, self).__init__() + + self.layers = nn.ModuleList() + for i_layer in range(n_layers): + self.layers.append(nn.Conv2d(n_channels, n_channels, + kernel_size=kernel, stride=stride, + padding=(kernel - stride) // 2, + bias= not use_batch_norm)) + if use_batch_norm: + self.layers.append(nn.BatchNorm2d(n_channels)) + if i_layer!=(n_layers - 1): + self.layers.append(nn.ReLU(inplace=True)) + + self.downsample = downsample # Optional downsampling layer + self.post_residual_layers = nn.ModuleList([nn.ReLU(inplace=True)]) + if pool_size is not None: + self.post_residual_layers.append(nn.MaxPool2d(kernel_size=pool_size)) + + def forward(self, x): + identity = x + + # Pass through the layers in the ModuleList + for layer in self.layers: + x = layer(x) + + # Apply downsampling to the identity if necessary + if self.downsample: + identity = self.downsample(identity) + + # Add the residual connection + x += identity + for layer in self.post_residual_layers: + x = layer(x) + + return x + +class Encoder(nn.Module): + + def __init__(self, + configs): + """ + Args: + in_channels (int): Number of input channels. + out_channels (int): Number of output channels. + stride (int): Stride for the convolutional layers. + """ + super(Encoder, self).__init__() + + self.configs = configs + + self.residual_layers = nn.ModuleList() + + for i_blocks in range(configs['num_blocks']): + self.residual_layers.append(PreConvBlock(configs['in_channels_%d'%i_blocks], + configs['out_channels_%d'%i_blocks], + configs['kernel_preconv'], + configs['stride_preconv'], + configs['pool_size_preconv_%d'%i_blocks], + configs['use_batch_norm_preconv'])) + self.residual_layers.append(ResidualBlock(configs['out_channels_%d'%i_blocks], + configs['kernel_residual'], + configs['stride_residual'], + configs['use_batch_norm_residual'], + pool_size=configs['pool_size_residual_%d'%i_blocks], + n_layers=configs['n_layers_residual'])) + + self.linear_layers = nn.ModuleList([nn.Linear(configs['out_conv'], configs['out_linear'], bias = not configs['use_batch_norm_linear']),]) + if configs['use_batch_norm_linear']: + self.linear_layers.append(nn.BatchNorm1d(configs['out_linear'])) + self.linear_layers.append(nn.ReLU(inplace=True)) + self.linear_layers.append(nn.Linear(configs['out_linear'], configs['embed_size'])) + + def forward(self, x): + bs, seq_length, c, h ,w = x.size() + + x = x.view(bs*seq_length, c, h, w) + for layer in self.residual_layers: + x = layer(x) + + x = x.view(bs*seq_length, -1) + for layer in self.linear_layers: + x = layer(x) + + x = x.view(bs, seq_length, -1) + return x + +class SingleSessionDecoder(nn.Module): + def __init__(self, configs): + super(SingleSessionDecoder, self).__init__() + self.configs = configs + self.linear_layer = nn.Linear(configs['embed_size'], configs['image_height']*configs['image_width']) + + def forward(self, x): + bs, seq_length, _ = x.size() + x = self.linear_layer(x) + x = x.view(bs, seq_length, 1, self.configs['image_height'], self.configs['image_width']) + return x + +class SingleSessionAutoEncoder(nn.Module): + """ + This autoencoder takes in data of shape (batch,sequence (for a sequence of images), channels (1 for grayscale), height, width). Thus, it is set up to work with multidimensional, video structured data, although in practice we work with grayscale and can usually flatten across a sequence of images for the examples that we often care about. The latents are of shape batch,sequence,embedding_size. + """ + def __init__(self, configs): + super(SingleSessionAutoEncoder, self).__init__() + self.configs = configs + self.encoder = Encoder(configs) + self.decoder = SingleSessionDecoder(configs) + + def forward(self, x): + z = self.encoder(x) + x = self.decoder(z) + return x, z From 602a8e2fe07cefbd6de429989aa9d3d8ceaccfd3 Mon Sep 17 00:00:00 2001 From: Taiga Abe Date: Tue, 4 Mar 2025 17:24:26 -0800 Subject: [PATCH 04/34] added rudimentary tests of function for all components. Can get a baby training loop to train. --- tests/test_dataloading.py | 6 +- tests/test_dataset.py | 4 +- tests/test_module.py | 136 ++++++++++++++++++++++++++++++++++++++ tests/test_networks.py | 28 ++++++++ 4 files changed, 170 insertions(+), 4 deletions(-) create mode 100644 tests/test_module.py create mode 100644 tests/test_networks.py diff --git a/tests/test_dataloading.py b/tests/test_dataloading.py index 83224a6..a407112 100644 --- a/tests/test_dataloading.py +++ b/tests/test_dataloading.py @@ -98,7 +98,7 @@ def test_init(self,tmp_path): "trial_pattern":None } sfdm = SessionFramesDataModule(data_config,10,2,10,1) - assert sfdm.mean_image.shape == (crop_config["target_h"],crop_config["target_w"]) + assert sfdm.mean_image.shape == (1,crop_config["target_h"],crop_config["target_w"]) def test_train_dataloader(self,tmp_path): with open(self.config_path,"r") as f: @@ -121,6 +121,6 @@ def test_train_dataloader(self,tmp_path): } sfdm = SessionFramesDataModule(data_config,10,2,10,1,) dataloader = sfdm.train_dataloader() - for batch in dataloader: - assert batch.shape[1:] == (crop_config["target_h"],crop_config["target_w"]) + for batch in dataloader: + assert batch.shape[1:] == (1,1,crop_config["target_h"],crop_config["target_w"]) diff --git a/tests/test_dataset.py b/tests/test_dataset.py index 897aa60..c8781f8 100644 --- a/tests/test_dataset.py +++ b/tests/test_dataset.py @@ -285,7 +285,9 @@ def test_init(self, temp_hierarchical_folder): # Test image properties first_image = dataset[0] assert isinstance(first_image, torch.Tensor), "Dataset should return numpy arrays" - assert first_image.shape[0] == 1, "Images should be 2D grayscale (H,W)" + assert len(first_image.shape) == 4 + assert first_image.shape[0] == 1, "Sequence dimension should be 0" + assert first_image.shape[1] == 1, "Images should be 2D grayscale (H,W)" assert first_image.dtype == torch.float32, "Images should be float32" # Test dataset length diff --git a/tests/test_module.py b/tests/test_module.py new file mode 100644 index 0000000..40afcf5 --- /dev/null +++ b/tests/test_module.py @@ -0,0 +1,136 @@ +""" +""" +from behavioral_autoencoder.module import SingleSessionModule +from behavioral_autoencoder.dataset import CropResizeProportion +from behavioral_autoencoder.dataloading import SessionFramesDataModule +from pytorch_lightning.loggers import TensorBoardLogger +import numpy as np +import pytorch_lightning as pl +import cv2 +import os +import json + +here = os.path.join(os.path.abspath(os.path.dirname(__file__))) + +def temp_hierarchical_folder_generator(tmp_path, n_trials=3, n_ims_per_trial=10, extra_files=None): + """Creates a temporary hierarchical folder structure for testing. + + This fixture creates a folder structure that mimics a session with multiple trials, + where each trial contains randomly sampled images. The structure is: + temp_session/ + ├── 0_trial/ + │ ├── frame_000000.png + │ ├── frame_000001.png + │ ├── ... + │ └── extra_file.txt + ├── 1_trial/ + │ ├── frame_000000.png + │ ├── ... + │ └── extra_file.txt + └── ... + + Parameters + ---------- + tmp_path : Path + Pytest fixture providing temporary directory path + n_trials : int, optional + Number of trial folders to create, by default 3 + n_ims_per_trial : int, optional + Number of images to create per trial, by default 10 + extra_files : list of str, optional + List of extra file names to create in each trial directory + + Returns + ------- + Path + Path to the created temporary session directory + """ + # Set random seed for reproducibility + np.random.seed(42) + + # Load example images + example_images = np.load('./test_data/example_images.npy')*255 + + # Create session directory + session_dir = tmp_path / "temp_session" + session_dir.mkdir() + + # Create trial folders and populate with images + for trial in range(n_trials): + trial_dir = session_dir / f"{trial}_trial" + trial_dir.mkdir() + + # Randomly sample images + selected_images = example_images[ + np.random.choice(len(example_images), n_ims_per_trial, replace=True) + ] + + # Save images as PNGs + for i, img in enumerate(selected_images): + img_path = trial_dir / f"frame_{i:06d}.png" + cv2.imwrite(str(img_path), img) + + # Create extra files within each trial folder if specified + if extra_files: + for filename in extra_files: + (trial_dir / filename).touch() + + return session_dir + +class Test_SingleSessionModule(): + model_config_path = os.path.join(here,"..","configs","model_configs","alm_default.json") + train_config_path = os.path.join(here,"..","configs","train_configs","alm_default.json") + crop_config_path = os.path.join(here,"..","configs","crop_configs","alm_side.json") + def test_init(self): + with open(self.model_config_path,"r") as f: + model_config = json.load(f) + with open(self.train_config_path,"r") as f: + train_config = json.load(f) + hparams = { + "model":"single_session_autoencoder", + "model_config":model_config, + "train_config":train_config + } + ssm = SingleSessionModule(hparams) + def test_baby_train_loop(self,tmp_path): + model_config_path = os.path.join(here,"..","configs","model_configs","alm_default.json") + train_config_path = os.path.join(here,"..","configs","train_configs","alm_default.json") + + with open(self.model_config_path,"r") as f: + model_config = json.load(f) + with open(self.train_config_path,"r") as f: + train_config = json.load(f) + with open(self.crop_config_path,"r") as f: + crop_config = json.load(f) + + hparams = { + "model":"single_session_autoencoder", + "model_config":model_config, + "train_config":train_config + } + ssm = SingleSessionModule(hparams) + + session_dir = temp_hierarchical_folder_generator( + tmp_path, + ) + + alm_cropping = CropResizeProportion(self.crop_config_path) + data_config = { + "data_path":session_dir, + "transform":alm_cropping, + "extension":".png", + "trial_pattern":None + } + sfdm = SessionFramesDataModule(data_config,10,2,10,1) + logger = TensorBoardLogger("tb_logs",name="test_single_session_auto",log_graph=True) + trainer = pl.Trainer( + max_epochs=1, + accelerator='cpu', # Explicitly use CPU for testing + enable_checkpointing=False, # Disable for testing + logger=logger, # Disable logging for testing + enable_progress_bar=True, # See training progress + ) + trainer.fit(ssm,sfdm) + assert trainer.current_epoch == 1, "Training should complete 2 epochs" + assert trainer.global_step > 0, "Should have completed some training steps" + diff --git a/tests/test_networks.py b/tests/test_networks.py new file mode 100644 index 0000000..ed23580 --- /dev/null +++ b/tests/test_networks.py @@ -0,0 +1,28 @@ +""" +Test that networks work as intended. +""" +import os +import torch +import json +from behavioral_autoencoder.networks import SingleSessionAutoEncoder + +here = os.path.abspath(os.path.dirname(__file__)) + +class Test_SingleSessionAutoEncoder(): + config_path = os.path.join(here,"..","configs","model_configs","alm_default.json") + def test_init(self): + with open(self.config_path,"r") as f: + config = json.load(f) + ssae = SingleSessionAutoEncoder(config) + def test_forward(self): + with open(self.config_path,"r") as f: + config = json.load(f) + batch_size=10 + dummy_input = torch.zeros(10,1,1,config["image_height"],config["image_width"]) + ssae = SingleSessionAutoEncoder(config) + output = ssae(dummy_input) + assert output[0].shape == (10,1,1,config["image_height"],config["image_width"]) + assert output[1].shape == (10,1,config["embed_size"]) + + + From 16ae2584690a660e50057644b4345d4af126ed31 Mon Sep 17 00:00:00 2001 From: Taiga Abe Date: Tue, 4 Mar 2025 17:24:53 -0800 Subject: [PATCH 05/34] added config params for training of ALM network. --- configs/model_configs/alm_default.json | 24 ++++++++++++++++++++++++ configs/train_configs/alm_default.json | 10 ++++++++++ 2 files changed, 34 insertions(+) create mode 100644 configs/model_configs/alm_default.json create mode 100644 configs/train_configs/alm_default.json diff --git a/configs/model_configs/alm_default.json b/configs/model_configs/alm_default.json new file mode 100644 index 0000000..36d2db6 --- /dev/null +++ b/configs/model_configs/alm_default.json @@ -0,0 +1,24 @@ +{ +"num_blocks": 2, +"in_channels_0": 1, +"out_channels_0": 16, +"in_channels_1": 16, +"out_channels_1": 32, +"kernel_preconv": 3, +"stride_preconv": 1, +"pool_size_preconv_0": 2, +"pool_size_preconv_1": 4, +"use_batch_norm_preconv": false, +"kernel_residual": 3, +"stride_residual": 1, +"use_batch_norm_residual": false, +"pool_size_residual_0": null, +"pool_size_residual_1": 4, +"n_layers_residual": 3, +"out_conv": 288, +"out_linear": 128, +"embed_size": 16, +"use_batch_norm_linear": false, +"image_height": 120, +"image_width": 112 +} diff --git a/configs/train_configs/alm_default.json b/configs/train_configs/alm_default.json new file mode 100644 index 0000000..b8d6b29 --- /dev/null +++ b/configs/train_configs/alm_default.json @@ -0,0 +1,10 @@ +{ +"scheduler":"linear", +"start_factor":0.1, +"end_factor":1, +"warmup_steps":10, +"max_epochs":500, +"weight_decay":0, +"l2_weight":1, +"learning_rate":0 +} From f8f0ca6de5e1d8cbc953308631650e850ea05ad3 Mon Sep 17 00:00:00 2001 From: Taiga Abe Date: Tue, 4 Mar 2025 18:18:35 -0800 Subject: [PATCH 06/34] have a script that should in theory train --- scripts/train_single_session.py | 90 +++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 scripts/train_single_session.py diff --git a/scripts/train_single_session.py b/scripts/train_single_session.py new file mode 100644 index 0000000..1575149 --- /dev/null +++ b/scripts/train_single_session.py @@ -0,0 +1,90 @@ +"""Train a single session autoencoder on provided data. + +Saves checkpoints for the corresponding model, outputs to tensorboard, and finally dumps all predictions and latents into a save directory. + +""" +import os +import fire +import joblib +import datetime +from behavioral_autoencoder.module import SingleSessionModule +from behavioral_autoencoder.dataset import CropResizeProportion +from behavioral_autoencoder.dataloading import SessionFramesDataModule +from behavioral_autoencoder.eval import get_all_predicts_latents +from pytorch_lightning.callbacks import ModelCheckpoint +from pytorch_lightning.loggers import TensorBoardLogger + +here = os.path.join(os.path.abspath(os.path.dirname(__file__))) + +def main(model_config_path,train_config_path,data_path,crop_config_path): + """This main function takes as input four paths. These paths indicate the model configuration parameters, training configuration parameters, path to the data directory, and cropping configuration, respectively. By default we assume that we are training a single session autoencoder. + """ + ## Model setup + with open(model_config_path,"r") as f: + model_config = json.load(f) + with open(train_config_path,"r") as f: + train_config = json.load(f) + model_name = "single_session_autoencoder" + + hparams = { + "model":model_name, + "model_config":model_config, + "train_config":train_config + } + + ssm = SingleSessionModule(hparams) + + ## Data setup + alm_cropping = CropResizeProportion(crop_config_path) + data_config = { + "data_path":session_dir, + "transform":alm_cropping, + "extension":".png", + "trial_pattern":None + } + sfdm = SessionFramesDataModule( + data_config, + train_config["batch_size"], + train_config["num_workers"], + train_config["subsample_rate"], + train_config["subsample_offset"]) + + ## Set up logging and trainer + date=datetime.datetime.now().strftime("%m-%d-%y") + time=datetime.datetime.now().strftime("%H_%M_%S") + timestamp_model = os.path.join(here,"models",model_name,date,time) + timestamp_pred = os.path.join(here,"preds",pred_name,date,time) + logger = TensorBoardLogger("tb_logs",name="test_single_session_auto",log_graph=True) + + checkpoint = ModelCheckpoint(monitor="acc/mse", mode="max", save_last=False, dirpath = outdir) + + trainer = pl.Trainer( + fast_dev_run=train_config["fast_dev_run"], + max_epochs=train_config["max_epochs"], + accelerator=train_config["accelerator"], # Explicitly use CPU for testing + enable_checkpointing=True, # Disable for testing + checkpoint_callback = checkpoint, + log_every_n_steps = 1, + logger=logger, # Disable logging for testing + enable_progress_bar=True, # See training progress + ) + + ## Fit the model + trainer.fit(ssm,sfdm) + + ## Get out predictions + preds,latents = get_all_predicts_latents(ssm,sfdm,train_config["batch_size"],train_config["num_workers"]) + + ## Save out all relevant metadata. + os.mkdir(timestamp_pred) + joblib.dump(preds,os.path.join(timestamp_pred,"preds")) + joblib.dump(latents,os.path.join(timestamp_pred,"latents")) + with open(os.path.join(timestamp_pred,"model_config"),"w") as f: + json.dump(hparams) + + with open(os.path.join(timestamp_pred,"data_config"),"w") as f: + json.dump(data_config) + + +if __name__ == "__main__": + fire.Fire(main) From 38cf7136d81e0b7cf4bc603b968df8bf562a296f Mon Sep 17 00:00:00 2001 From: Taiga Abe Date: Tue, 4 Mar 2025 22:50:09 -0800 Subject: [PATCH 07/34] got training script to run in dev mode. --- scripts/train_single_session.py | 64 ++++++++++++++++++++---------- src/behavioral_autoencoder/eval.py | 22 ++++++++++ 2 files changed, 66 insertions(+), 20 deletions(-) create mode 100644 src/behavioral_autoencoder/eval.py diff --git a/scripts/train_single_session.py b/scripts/train_single_session.py index 1575149..6f36ee2 100644 --- a/scripts/train_single_session.py +++ b/scripts/train_single_session.py @@ -5,8 +5,10 @@ """ import os import fire +import json import joblib import datetime +import pytorch_lightning as pl from behavioral_autoencoder.module import SingleSessionModule from behavioral_autoencoder.dataset import CropResizeProportion from behavioral_autoencoder.dataloading import SessionFramesDataModule @@ -16,32 +18,45 @@ here = os.path.join(os.path.abspath(os.path.dirname(__file__))) -def main(model_config_path,train_config_path,data_path,crop_config_path): +def main(model_config_path, train_config_path, data_path, data_config_path): """This main function takes as input four paths. These paths indicate the model configuration parameters, training configuration parameters, path to the data directory, and cropping configuration, respectively. By default we assume that we are training a single session autoencoder. """ + print("\n=== Starting Single Session Autoencoder Training ===") + ## Model setup + print("\nLoading configurations...") with open(model_config_path,"r") as f: model_config = json.load(f) with open(train_config_path,"r") as f: train_config = json.load(f) model_name = "single_session_autoencoder" + print(f"Model config: {model_config}") + print(f"Training config: {train_config}") + hparams = { "model":model_name, "model_config":model_config, "train_config":train_config } + print("\nInitializing model...") ssm = SingleSessionModule(hparams) ## Data setup - alm_cropping = CropResizeProportion(crop_config_path) + with open(data_config_path,"r") as f: + data_process_config = json.load(f) + print("\nSetting up data...") + alm_cropping = CropResizeProportion(data_config_path) data_config = { - "data_path":session_dir, + "data_path":data_path, "transform":alm_cropping, - "extension":".png", - "trial_pattern":None + "extension":data_process_config["extension"], + "trial_pattern":data_process_config["trial_pattern"] } + print(f"Data config: {data_config}") + + print("Initializing data module...") sfdm = SessionFramesDataModule( data_config, train_config["batch_size"], @@ -50,41 +65,50 @@ def main(model_config_path,train_config_path,data_path,crop_config_path): train_config["subsample_offset"]) ## Set up logging and trainer + print("\nSetting up logging and checkpoints...") date=datetime.datetime.now().strftime("%m-%d-%y") time=datetime.datetime.now().strftime("%H_%M_%S") timestamp_model = os.path.join(here,"models",model_name,date,time) - timestamp_pred = os.path.join(here,"preds",pred_name,date,time) + timestamp_pred = os.path.join(here,"preds",model_name,date,time) + print(f"Model will be saved to: {timestamp_model}") + print(f"Predictions will be saved to: {timestamp_pred}") + logger = TensorBoardLogger("tb_logs",name="test_single_session_auto",log_graph=True) + checkpoint = ModelCheckpoint(monitor="mse/val", mode="max", save_last=False, dirpath=timestamp_model) - checkpoint = ModelCheckpoint(monitor="acc/mse", mode="max", save_last=False, dirpath = outdir) - + print("\nInitializing trainer...") trainer = pl.Trainer( fast_dev_run=train_config["fast_dev_run"], max_epochs=train_config["max_epochs"], - accelerator=train_config["accelerator"], # Explicitly use CPU for testing - enable_checkpointing=True, # Disable for testing - checkpoint_callback = checkpoint, - log_every_n_steps = 1, - logger=logger, # Disable logging for testing - enable_progress_bar=True, # See training progress + accelerator=train_config["accelerator"], + enable_checkpointing=True, + callbacks=[checkpoint], + log_every_n_steps=1, + logger=logger, + enable_progress_bar=True, ) ## Fit the model + print(f"\nStarting training for {train_config['max_epochs']} epochs...") trainer.fit(ssm,sfdm) + print("Training completed!") ## Get out predictions + print("\nGenerating predictions and latents...") preds,latents = get_all_predicts_latents(ssm,sfdm,train_config["batch_size"],train_config["num_workers"]) - ## Save out all relevant metadata. - os.mkdir(timestamp_pred) + ## Save out all relevant metadata + print("\nSaving results...") + os.makedirs(timestamp_pred, exist_ok=True) joblib.dump(preds,os.path.join(timestamp_pred,"preds")) joblib.dump(latents,os.path.join(timestamp_pred,"latents")) with open(os.path.join(timestamp_pred,"model_config"),"w") as f: - json.dump(hparams) - + json.dump(hparams, f) with open(os.path.join(timestamp_pred,"data_config"),"w") as f: - json.dump(data_config) - + json.dump(data_config, f) + + print("\n=== Training Complete ===") + print(f"Results saved to: {timestamp_pred}") if __name__ == "__main__": fire.Fire(main) diff --git a/src/behavioral_autoencoder/eval.py b/src/behavioral_autoencoder/eval.py new file mode 100644 index 0000000..9b76689 --- /dev/null +++ b/src/behavioral_autoencoder/eval.py @@ -0,0 +1,22 @@ +""" +Functions for two kinds of evaluation: + 1. Bulk extraction of latents and predictions from the last timepoint. + 2. Evaluation of the latent space. +""" +from torch.utils.data import DataLoader +import torch +from tqdm import tqdm + +def get_all_predicts_latents(trained_model,datamodule,batch_size,num_workers): + """ + Get latents for the entire video, having trained on a subsampled set. + + """ + full_dataloader = DataLoader(datamodule.dataset,batch_size=batch_size,num_workers=num_workers) + predictions = [] + latents = [] + for batch in tqdm(full_dataloader): + prediction,latent = trained_model(batch) + predictions.append(prediction) + latents.append(latent) + return torch.concatenate(predictions), torch.concatenate(latents) From e7ec537dcc4c216ec8a2588792e14e4c1fc0a26c Mon Sep 17 00:00:00 2001 From: Taiga Abe Date: Tue, 4 Mar 2025 22:50:42 -0800 Subject: [PATCH 08/34] added configs for debugging and training. --- configs/data_configs/alm_side.json | 8 ++++++++ configs/train_configs/alm_default_dev.json | 17 +++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 configs/data_configs/alm_side.json create mode 100644 configs/train_configs/alm_default_dev.json diff --git a/configs/data_configs/alm_side.json b/configs/data_configs/alm_side.json new file mode 100644 index 0000000..c0f291d --- /dev/null +++ b/configs/data_configs/alm_side.json @@ -0,0 +1,8 @@ +{ +"proportional_h_coord_top": 0.21666666666666667, +"target_h": 120, +"target_w": 112, +"extension": ".png", +"trial_pattern": "^CW44_20240522153954_side_trial_" + +} diff --git a/configs/train_configs/alm_default_dev.json b/configs/train_configs/alm_default_dev.json new file mode 100644 index 0000000..cd48064 --- /dev/null +++ b/configs/train_configs/alm_default_dev.json @@ -0,0 +1,17 @@ +{ +"scheduler":"linear", +"start_factor":0.1, +"end_factor":1, +"warmup_steps":10, +"max_epochs":500, +"weight_decay":0, +"l2_weight":1, +"learning_rate":0, +"batch_size":10, +"num_workers":2, +"subsample_rate":10, +"subsample_offset":0, +"max_epochs":100, +"accelerator":"cpu", +"fast_dev_run":true +} From 75d36be39943fcb9a8cc2c870d0808316031c380 Mon Sep 17 00:00:00 2001 From: Taiga Abe Date: Tue, 4 Mar 2025 22:52:15 -0800 Subject: [PATCH 09/34] fixed some attributes in dataloading. --- configs/train_configs/alm_default.json | 9 ++++++++- requirements_cpu.txt | 14 ++++++++++---- src/behavioral_autoencoder/data_utils.py | 1 - src/behavioral_autoencoder/dataloading.py | 8 ++++---- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/configs/train_configs/alm_default.json b/configs/train_configs/alm_default.json index b8d6b29..5d4e0fd 100644 --- a/configs/train_configs/alm_default.json +++ b/configs/train_configs/alm_default.json @@ -6,5 +6,12 @@ "max_epochs":500, "weight_decay":0, "l2_weight":1, -"learning_rate":0 +"learning_rate":0, +"batch_size":10, +"num_workers":2, +"subsample_rate":10, +"subsample_offset":0, +"max_epochs":500, +"accelerator":"gpu" +"fast_dev_run":false } diff --git a/requirements_cpu.txt b/requirements_cpu.txt index 7fd84b2..5b3fc31 100644 --- a/requirements_cpu.txt +++ b/requirements_cpu.txt @@ -1,4 +1,10 @@ -pip install torch # should specify a version later -pip install numpy -pip install matplotlib -pip install pytest +torch # should specify a version later +torchvision +joblib +pytorch_lightning +numpy +matplotlib +pytest +opencv-python +fire +scikit-image diff --git a/src/behavioral_autoencoder/data_utils.py b/src/behavioral_autoencoder/data_utils.py index 2b4823b..308b5d5 100644 --- a/src/behavioral_autoencoder/data_utils.py +++ b/src/behavioral_autoencoder/data_utils.py @@ -99,7 +99,6 @@ def main(video_dir,frame_dir,video_suffix=".avi"): with open(frame_dir / "annotations.txt", "a") as f: f.write(f"{video_dir_name} {first} {last} 0\n") -def npy_to_frames() if __name__ == "__main__": fire.Fire(main) diff --git a/src/behavioral_autoencoder/dataloading.py b/src/behavioral_autoencoder/dataloading.py index 67bab10..5fcde8d 100644 --- a/src/behavioral_autoencoder/dataloading.py +++ b/src/behavioral_autoencoder/dataloading.py @@ -103,13 +103,13 @@ def subtract_mean_image(self,x): def setup(self,stage): _ = self.dataset_config.pop("transform") - dataset = SessionFramesTorchvision(self.data_path,transform = self.transform,**self.dataset_config) - all_indices = np.arange(len(dataset)) + self.dataset = SessionFramesTorchvision(self.data_path,transform = self.transform,**self.dataset_config) + all_indices = np.arange(len(self.dataset)) ## Subsample indices train_inds = all_indices[self.subsample_offset::self.subsample_rate] test_inds = [i for i in all_indices if not (i in train_inds)] - self.trainset = Subset(dataset,train_inds) - self.valset = Subset(dataset,test_inds) + self.trainset = Subset(self.dataset,train_inds) + self.valset = Subset(self.dataset,test_inds) def train_dataloader(self,shuffle=True): dataloader = DataLoader( From 111a8081f29d0cc59739ce91c931af4001edc119 Mon Sep 17 00:00:00 2001 From: Taiga Abe Date: Tue, 4 Mar 2025 22:52:41 -0800 Subject: [PATCH 10/34] deleted crop configs --- configs/crop_configs/alm_side.json | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 configs/crop_configs/alm_side.json diff --git a/configs/crop_configs/alm_side.json b/configs/crop_configs/alm_side.json deleted file mode 100644 index d0419dd..0000000 --- a/configs/crop_configs/alm_side.json +++ /dev/null @@ -1,5 +0,0 @@ -{ -"proportional_h_coord_top": 0.21666666666666667, -"target_h": 120, -"target_w": 112 -} From a005bf894102ab0cc38023ec0aa85892d1617ce9 Mon Sep 17 00:00:00 2001 From: Taiga Abe Date: Tue, 4 Mar 2025 22:53:01 -0800 Subject: [PATCH 11/34] fixed up tests for data configs. --- tests/test_dataloading.py | 2 +- tests/test_dataset.py | 2 +- tests/test_module.py | 50 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/tests/test_dataloading.py b/tests/test_dataloading.py index a407112..b94103c 100644 --- a/tests/test_dataloading.py +++ b/tests/test_dataloading.py @@ -77,7 +77,7 @@ def temp_hierarchical_folder_generator(tmp_path, n_trials=3, n_ims_per_trial=10, return session_dir class Test_SessionFramesDataModule(): - config_path = os.path.join(here,"..","configs","crop_configs","alm_side.json") + config_path = os.path.join(here,"..","configs","data_configs","alm_side.json") def test_init(self,tmp_path): with open(self.config_path,"r") as f: crop_config = json.load(f) diff --git a/tests/test_dataset.py b/tests/test_dataset.py index c8781f8..fd263a5 100644 --- a/tests/test_dataset.py +++ b/tests/test_dataset.py @@ -264,7 +264,7 @@ def test_getitem_performance(self, tmp_path): f"Methods returned different results for index {idx}" class Test_SessionFramesTorchvision: - config_path = os.path.join(here,"..","configs","crop_configs","alm_side.json") + config_path = os.path.join(here,"..","configs","data_configs","alm_side.json") def test_init(self, temp_hierarchical_folder): """Test the SessionFramesTorchvision initialization and basic properties, including with default cropping given by CropResizeProportion with default. diff --git a/tests/test_module.py b/tests/test_module.py index 40afcf5..c9d0aca 100644 --- a/tests/test_module.py +++ b/tests/test_module.py @@ -5,6 +5,7 @@ from behavioral_autoencoder.dataloading import SessionFramesDataModule from pytorch_lightning.loggers import TensorBoardLogger import numpy as np +from torch.utils.data import DataLoader import pytorch_lightning as pl import cv2 import os @@ -80,7 +81,7 @@ def temp_hierarchical_folder_generator(tmp_path, n_trials=3, n_ims_per_trial=10, class Test_SingleSessionModule(): model_config_path = os.path.join(here,"..","configs","model_configs","alm_default.json") train_config_path = os.path.join(here,"..","configs","train_configs","alm_default.json") - crop_config_path = os.path.join(here,"..","configs","crop_configs","alm_side.json") + crop_config_path = os.path.join(here,"..","configs","data_configs","alm_side.json") def test_init(self): with open(self.model_config_path,"r") as f: model_config = json.load(f) @@ -133,4 +134,51 @@ def test_baby_train_loop(self,tmp_path): trainer.fit(ssm,sfdm) assert trainer.current_epoch == 1, "Training should complete 2 epochs" assert trainer.global_step > 0, "Should have completed some training steps" + def test_eval_save(self,tmp_path): + model_config_path = os.path.join(here,"..","configs","model_configs","alm_default.json") + train_config_path = os.path.join(here,"..","configs","train_configs","alm_default.json") + + with open(self.model_config_path,"r") as f: + model_config = json.load(f) + with open(self.train_config_path,"r") as f: + train_config = json.load(f) + with open(self.crop_config_path,"r") as f: + crop_config = json.load(f) + + hparams = { + "model":"single_session_autoencoder", + "model_config":model_config, + "train_config":train_config + } + ssm = SingleSessionModule(hparams) + + session_dir = temp_hierarchical_folder_generator( + tmp_path, + ) + + alm_cropping = CropResizeProportion(self.crop_config_path) + data_config = { + "data_path":session_dir, + "transform":alm_cropping, + "extension":".png", + "trial_pattern":None + } + sfdm = SessionFramesDataModule(data_config,10,2,10,1) + logger = TensorBoardLogger("tb_logs",name="test_single_session_auto",log_graph=True) + trainer = pl.Trainer( + max_epochs=1, + accelerator='cpu', # Explicitly use CPU for testing + enable_checkpointing=False, # Disable for testing + logger=logger, # Disable logging for testing + enable_progress_bar=True, # See training progress + ) + trainer.fit(ssm,sfdm) + # Extract out some latents: + full_dataloader = DataLoader(sfdm.dataset,batch_size=10) + predictions = [] + latents = [] + for batch in full_dataloader: + prediction,latent = ssm(batch) + predictions.append() + import pdb; pdb.set_trace() From 7a43ab613b0fff0376094d541f6b78e5420d9e26 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 5 Mar 2025 06:57:16 +0000 Subject: [PATCH 12/34] added some docstring for bad stub --- src/behavioral_autoencoder/data_utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/behavioral_autoencoder/data_utils.py b/src/behavioral_autoencoder/data_utils.py index 2b4823b..e18f4f6 100644 --- a/src/behavioral_autoencoder/data_utils.py +++ b/src/behavioral_autoencoder/data_utils.py @@ -99,7 +99,10 @@ def main(video_dir,frame_dir,video_suffix=".avi"): with open(frame_dir / "annotations.txt", "a") as f: f.write(f"{video_dir_name} {first} {last} 0\n") -def npy_to_frames() +def npy_to_frames(): + """ + """ + if __name__ == "__main__": fire.Fire(main) From 8de0168e95905fe55181cd5526b23b253abbc448 Mon Sep 17 00:00:00 2001 From: Taiga Abe Date: Tue, 4 Mar 2025 23:15:35 -0800 Subject: [PATCH 13/34] some messaging for the dataloading. --- src/behavioral_autoencoder/dataloading.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/behavioral_autoencoder/dataloading.py b/src/behavioral_autoencoder/dataloading.py index 5fcde8d..275fd57 100644 --- a/src/behavioral_autoencoder/dataloading.py +++ b/src/behavioral_autoencoder/dataloading.py @@ -82,6 +82,7 @@ def __init__( self.subtract_mean = trainset_subtract_mean if self.subtract_mean: + print("Calculating mean image") self.mean_image = calculate_mean_image(self.data_path,dataset_config,self.subsample_rate,self.subsample_offset) subtract_mean = torchvision.transforms.Lambda(self.subtract_mean_image) # augment the transformation for our datasets: @@ -97,6 +98,7 @@ def __init__( self.transform = mean_normed_transform else: self.transform = self.dataset_config["transform"] + print("Done with setup") def subtract_mean_image(self,x): return x-self.mean_image From 5c984415946411f5c34090e03144820147b4dafb Mon Sep 17 00:00:00 2001 From: Taiga Abe Date: Tue, 4 Mar 2025 23:18:37 -0800 Subject: [PATCH 14/34] updated mean image calc. --- src/behavioral_autoencoder/dataloading.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/behavioral_autoencoder/dataloading.py b/src/behavioral_autoencoder/dataloading.py index 275fd57..d16fb3b 100644 --- a/src/behavioral_autoencoder/dataloading.py +++ b/src/behavioral_autoencoder/dataloading.py @@ -1,4 +1,5 @@ import torchvision +from tqdm import tqdm import numpy as np import torch from torch.utils.data import Subset,DataLoader @@ -7,7 +8,7 @@ from joblib import Memory import os -def calculate_mean_image(data_path,dataset_config,subsample_rate,subsample_offset): +def calculate_mean_image(data_path,dataset_config,subsample_rate,subsample_offset,batch_size,num_workers): """ Calculate mean image from given training set parameters. Gets parameters from SessionFramesDataModule TODO: implement caching for this function. @@ -20,10 +21,10 @@ def calculate_mean_image(data_path,dataset_config,subsample_rate,subsample_offse trainset = Subset(dataset,train_inds) ## use dataloader to compute sum image - trainloader = DataLoader(trainset,batch_size=10) + trainloader = DataLoader(trainset,batch_size=batch_size,num_workers=num_workers) sum_im = torch.zeros(dataset[0].shape[1:]) ## should be - for data in trainloader: + for data in tqdm(trainloader): sum_im+=data.sum(axis=0).sum(axis=0) ## sum across the batch and sequence dimensions. mean=sum_im/len(trainset) return mean @@ -83,7 +84,7 @@ def __init__( if self.subtract_mean: print("Calculating mean image") - self.mean_image = calculate_mean_image(self.data_path,dataset_config,self.subsample_rate,self.subsample_offset) + self.mean_image = calculate_mean_image(self.data_path,dataset_config,self.subsample_rate,self.subsample_offset,self,batch_size,self.num_workers) subtract_mean = torchvision.transforms.Lambda(self.subtract_mean_image) # augment the transformation for our datasets: if self.dataset_config["transform"] is not None: From dd438091da58748d01e11fae07357cd37697d999 Mon Sep 17 00:00:00 2001 From: Taiga Abe Date: Tue, 4 Mar 2025 23:20:29 -0800 Subject: [PATCH 15/34] fixed argument passing --- src/behavioral_autoencoder/dataloading.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/behavioral_autoencoder/dataloading.py b/src/behavioral_autoencoder/dataloading.py index d16fb3b..04f3631 100644 --- a/src/behavioral_autoencoder/dataloading.py +++ b/src/behavioral_autoencoder/dataloading.py @@ -84,7 +84,7 @@ def __init__( if self.subtract_mean: print("Calculating mean image") - self.mean_image = calculate_mean_image(self.data_path,dataset_config,self.subsample_rate,self.subsample_offset,self,batch_size,self.num_workers) + self.mean_image = calculate_mean_image(self.data_path,dataset_config,self.subsample_rate,self.subsample_offset,self.batch_size,self.num_workers) subtract_mean = torchvision.transforms.Lambda(self.subtract_mean_image) # augment the transformation for our datasets: if self.dataset_config["transform"] is not None: From d1e7dc10a5009d6bd1dd6f184d17c18d0eb7d5c7 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 5 Mar 2025 07:43:28 +0000 Subject: [PATCH 16/34] trying to keep all predictions in memory is infeasible. save out training frames. --- scripts/train_single_session.py | 2 +- src/behavioral_autoencoder/eval.py | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/scripts/train_single_session.py b/scripts/train_single_session.py index 6f36ee2..8ec996d 100644 --- a/scripts/train_single_session.py +++ b/scripts/train_single_session.py @@ -95,7 +95,7 @@ def main(model_config_path, train_config_path, data_path, data_config_path): ## Get out predictions print("\nGenerating predictions and latents...") - preds,latents = get_all_predicts_latents(ssm,sfdm,train_config["batch_size"],train_config["num_workers"]) + preds,latents = get_dl_predicts_latents(ssm,sfdm.train_dataloader(),train_config["batch_size"],train_config["num_workers"]) ## Save out all relevant metadata print("\nSaving results...") diff --git a/src/behavioral_autoencoder/eval.py b/src/behavioral_autoencoder/eval.py index 9b76689..ddafb14 100644 --- a/src/behavioral_autoencoder/eval.py +++ b/src/behavioral_autoencoder/eval.py @@ -20,3 +20,16 @@ def get_all_predicts_latents(trained_model,datamodule,batch_size,num_workers): predictions.append(prediction) latents.append(latent) return torch.concatenate(predictions), torch.concatenate(latents) + +def get_dl_predicts_latents(trained_model,dataloader,batch_size,num_workers): + """ + Get latents for the entire video, having trained on a subsampled set. + + """ + predictions = [] + latents = [] + for batch in tqdm(dataloader): + prediction,latent = trained_model(batch) + predictions.append(prediction) + latents.append(latent) + return torch.concatenate(predictions), torch.concatenate(latents) From 7acd6f91bdc0127086fee82c372373142d1b7211 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 5 Mar 2025 07:52:31 +0000 Subject: [PATCH 17/34] tested additional flags on valset --- src/behavioral_autoencoder/dataloading.py | 23 ++++++++++++++--------- tests/test_dataloading.py | 5 +++-- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/behavioral_autoencoder/dataloading.py b/src/behavioral_autoencoder/dataloading.py index 04f3631..fcd7925 100644 --- a/src/behavioral_autoencoder/dataloading.py +++ b/src/behavioral_autoencoder/dataloading.py @@ -44,9 +44,11 @@ def __init__( dataset_config, batch_size, num_workers, - trainset_subsample_rate, - trainset_subsample_offset, - trainset_subtract_mean=True, + train_subsample_rate, + train_subsample_offset, + train_subtract_mean=True, + val_subsample_rate, + val_subsample_offset, ): """ By default, checks if we have already calculated the image mean on a particular dataset for a particular training set, and if so gets that cached mean. @@ -78,13 +80,15 @@ def __init__( self.dataset_config = dataset_config self.batch_size = batch_size self.num_workers = num_workers - self.subsample_rate = trainset_subsample_rate - self.subsample_offset = trainset_subsample_offset + self.train_subsample_rate = train_subsample_rate + self.train_subsample_offset = train_subsample_offset + self.val_subsample_rate = val_subsample_rate + self.val_subsample_offset = val_subsample_offset self.subtract_mean = trainset_subtract_mean if self.subtract_mean: print("Calculating mean image") - self.mean_image = calculate_mean_image(self.data_path,dataset_config,self.subsample_rate,self.subsample_offset,self.batch_size,self.num_workers) + self.mean_image = calculate_mean_image(self.data_path,dataset_config,self.train_subsample_rate,self.train_subsample_offset,self.batch_size,self.num_workers) subtract_mean = torchvision.transforms.Lambda(self.subtract_mean_image) # augment the transformation for our datasets: if self.dataset_config["transform"] is not None: @@ -109,10 +113,11 @@ def setup(self,stage): self.dataset = SessionFramesTorchvision(self.data_path,transform = self.transform,**self.dataset_config) all_indices = np.arange(len(self.dataset)) ## Subsample indices - train_inds = all_indices[self.subsample_offset::self.subsample_rate] - test_inds = [i for i in all_indices if not (i in train_inds)] + train_inds = all_indices[self.train_subsample_offset::self.train_subsample_rate] + val_inds = all_indices[self.val_subsample_offset::self.val_subsample_rate] + #test_inds = [i for i in all_indices if not (i in train_inds)] self.trainset = Subset(self.dataset,train_inds) - self.valset = Subset(self.dataset,test_inds) + self.valset = Subset(self.dataset,val_inds) def train_dataloader(self,shuffle=True): dataloader = DataLoader( diff --git a/tests/test_dataloading.py b/tests/test_dataloading.py index b94103c..928dbc2 100644 --- a/tests/test_dataloading.py +++ b/tests/test_dataloading.py @@ -97,7 +97,7 @@ def test_init(self,tmp_path): "extension":".png", "trial_pattern":None } - sfdm = SessionFramesDataModule(data_config,10,2,10,1) + sfdm = SessionFramesDataModule(data_config,10,2,10,1,10,1) assert sfdm.mean_image.shape == (1,crop_config["target_h"],crop_config["target_w"]) def test_train_dataloader(self,tmp_path): @@ -119,7 +119,8 @@ def test_train_dataloader(self,tmp_path): "extension":".png", "trial_pattern":None } - sfdm = SessionFramesDataModule(data_config,10,2,10,1,) + sfdm = SessionFramesDataModule(data_config,10,2,10,1,10,1) + sfdm.setup("train") dataloader = sfdm.train_dataloader() for batch in dataloader: From f15553c4d4eaaf7546ab7cfc126729176d8396d5 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 5 Mar 2025 07:53:33 +0000 Subject: [PATCH 18/34] eval test failing for some reason --- tests/test_module.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_module.py b/tests/test_module.py index c9d0aca..4e689a8 100644 --- a/tests/test_module.py +++ b/tests/test_module.py @@ -122,7 +122,7 @@ def test_baby_train_loop(self,tmp_path): "extension":".png", "trial_pattern":None } - sfdm = SessionFramesDataModule(data_config,10,2,10,1) + sfdm = SessionFramesDataModule(data_config,10,2,10,1,10,1) logger = TensorBoardLogger("tb_logs",name="test_single_session_auto",log_graph=True) trainer = pl.Trainer( max_epochs=1, @@ -163,7 +163,7 @@ def test_eval_save(self,tmp_path): "extension":".png", "trial_pattern":None } - sfdm = SessionFramesDataModule(data_config,10,2,10,1) + sfdm = SessionFramesDataModule(data_config,10,2,10,1,10,1) logger = TensorBoardLogger("tb_logs",name="test_single_session_auto",log_graph=True) trainer = pl.Trainer( max_epochs=1, From 08c20bbdb993ecfc6d47b09f0683628c6016e1a5 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 5 Mar 2025 08:11:12 +0000 Subject: [PATCH 19/34] adapted training for high frame count --- scripts/train_single_session.py | 7 +++++-- src/behavioral_autoencoder/dataloading.py | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/train_single_session.py b/scripts/train_single_session.py index 8ec996d..521b6dc 100644 --- a/scripts/train_single_session.py +++ b/scripts/train_single_session.py @@ -12,7 +12,7 @@ from behavioral_autoencoder.module import SingleSessionModule from behavioral_autoencoder.dataset import CropResizeProportion from behavioral_autoencoder.dataloading import SessionFramesDataModule -from behavioral_autoencoder.eval import get_all_predicts_latents +from behavioral_autoencoder.eval import get_all_predicts_latents,get_dl_predicts_latents from pytorch_lightning.callbacks import ModelCheckpoint from pytorch_lightning.loggers import TensorBoardLogger @@ -62,7 +62,10 @@ def main(model_config_path, train_config_path, data_path, data_config_path): train_config["batch_size"], train_config["num_workers"], train_config["subsample_rate"], - train_config["subsample_offset"]) + train_config["subsample_offset"], + train_config["val_subsample_rate"], + train_config["val_subsample_offset"] + ) ## Set up logging and trainer print("\nSetting up logging and checkpoints...") diff --git a/src/behavioral_autoencoder/dataloading.py b/src/behavioral_autoencoder/dataloading.py index fcd7925..8b90f7d 100644 --- a/src/behavioral_autoencoder/dataloading.py +++ b/src/behavioral_autoencoder/dataloading.py @@ -46,9 +46,9 @@ def __init__( num_workers, train_subsample_rate, train_subsample_offset, - train_subtract_mean=True, val_subsample_rate, val_subsample_offset, + trainset_subtract_mean=True, ): """ By default, checks if we have already calculated the image mean on a particular dataset for a particular training set, and if so gets that cached mean. From 4d8ade96913ff106729dfd35373446654d237aef Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 5 Mar 2025 08:18:30 +0000 Subject: [PATCH 20/34] added back mean image when predicting --- src/behavioral_autoencoder/eval.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/behavioral_autoencoder/eval.py b/src/behavioral_autoencoder/eval.py index ddafb14..6a3ff60 100644 --- a/src/behavioral_autoencoder/eval.py +++ b/src/behavioral_autoencoder/eval.py @@ -17,11 +17,11 @@ def get_all_predicts_latents(trained_model,datamodule,batch_size,num_workers): latents = [] for batch in tqdm(full_dataloader): prediction,latent = trained_model(batch) - predictions.append(prediction) + predictions.append(prediction+full_dataloader.mean_image[None,None,:]) latents.append(latent) return torch.concatenate(predictions), torch.concatenate(latents) -def get_dl_predicts_latents(trained_model,dataloader,batch_size,num_workers): +def get_dl_predicts_latents(trained_model,dataloader,mean,batch_size,num_workers): """ Get latents for the entire video, having trained on a subsampled set. @@ -30,6 +30,6 @@ def get_dl_predicts_latents(trained_model,dataloader,batch_size,num_workers): latents = [] for batch in tqdm(dataloader): prediction,latent = trained_model(batch) - predictions.append(prediction) + predictions.append(prediction+mean[None,None,:]) latents.append(latent) return torch.concatenate(predictions), torch.concatenate(latents) From da2ccfa5987f65dec904eaa9b804be82a723bb9e Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Thu, 6 Mar 2025 22:20:03 +0000 Subject: [PATCH 21/34] added learning rate monitoring and fixed checkpointing conditions. --- scripts/train_single_session.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/train_single_session.py b/scripts/train_single_session.py index 521b6dc..e1f223a 100644 --- a/scripts/train_single_session.py +++ b/scripts/train_single_session.py @@ -13,7 +13,7 @@ from behavioral_autoencoder.dataset import CropResizeProportion from behavioral_autoencoder.dataloading import SessionFramesDataModule from behavioral_autoencoder.eval import get_all_predicts_latents,get_dl_predicts_latents -from pytorch_lightning.callbacks import ModelCheckpoint +from pytorch_lightning.callbacks import ModelCheckpoint,LearningRateMonitor from pytorch_lightning.loggers import TensorBoardLogger here = os.path.join(os.path.abspath(os.path.dirname(__file__))) @@ -77,7 +77,8 @@ def main(model_config_path, train_config_path, data_path, data_config_path): print(f"Predictions will be saved to: {timestamp_pred}") logger = TensorBoardLogger("tb_logs",name="test_single_session_auto",log_graph=True) - checkpoint = ModelCheckpoint(monitor="mse/val", mode="max", save_last=False, dirpath=timestamp_model) + checkpoint = ModelCheckpoint(monitor="mse/val", mode="min", save_last=True, dirpath=timestamp_model) + lr_monitor = LearningRateMonitor(logging_interval='epoch') print("\nInitializing trainer...") trainer = pl.Trainer( @@ -85,7 +86,7 @@ def main(model_config_path, train_config_path, data_path, data_config_path): max_epochs=train_config["max_epochs"], accelerator=train_config["accelerator"], enable_checkpointing=True, - callbacks=[checkpoint], + callbacks=[checkpoint,lr_monitor], log_every_n_steps=1, logger=logger, enable_progress_bar=True, @@ -98,7 +99,7 @@ def main(model_config_path, train_config_path, data_path, data_config_path): ## Get out predictions print("\nGenerating predictions and latents...") - preds,latents = get_dl_predicts_latents(ssm,sfdm.train_dataloader(),train_config["batch_size"],train_config["num_workers"]) + preds,latents = get_dl_predicts_latents(ssm,sfdm.val_dataloader(),sfdm.mean_image,train_config["batch_size"],train_config["num_workers"]) ## Save out all relevant metadata print("\nSaving results...") From 01cbca0720f092bc25277212c4d1dd09293cfe3d Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Thu, 6 Mar 2025 22:20:36 +0000 Subject: [PATCH 22/34] played with learning rate scheduling. --- src/behavioral_autoencoder/module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/behavioral_autoencoder/module.py b/src/behavioral_autoencoder/module.py index 38a1749..c73c932 100644 --- a/src/behavioral_autoencoder/module.py +++ b/src/behavioral_autoencoder/module.py @@ -69,7 +69,7 @@ def setup_scheduler(self,optimizer): elif self.hparams["train_config"]["scheduler"] == "step": scheduler = { "scheduler": torch.optim.lr_scheduler.MultiStepLR( - optimizer, milestones = [60,120,160], gamma = 0.1, last_epoch=-1 + optimizer, milestones = [10,20,30], gamma = 0.1, last_epoch=-1 ), "interval": "epoch", "frequency":1, From ca177eeae0f9aa6115293affc7dc13b786cf6e07 Mon Sep 17 00:00:00 2001 From: Taiga Abe Date: Thu, 6 Mar 2025 14:58:51 -0800 Subject: [PATCH 23/34] added chained lr scheduler --- src/behavioral_autoencoder/module.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/behavioral_autoencoder/module.py b/src/behavioral_autoencoder/module.py index 38a1749..f48d78e 100644 --- a/src/behavioral_autoencoder/module.py +++ b/src/behavioral_autoencoder/module.py @@ -3,7 +3,7 @@ import torch.nn as nn from behavioral_autoencoder.metrics import L2_loss from behavioral_autoencoder.networks import SingleSessionAutoEncoder -from torch.optim.lr_scheduler import LinearLR +from torch.optim.lr_scheduler import LinearLR,ChainedScheduler models = { "single_session_autoencoder":SingleSessionAutoEncoder @@ -75,6 +75,25 @@ def setup_scheduler(self,optimizer): "frequency":1, "name": "learning_rate", } + elif self.hparams["train_config"]["scheduler"] == "linear_step": + linear_scheduler = { + "scheduler": LinearLR( + optimizer,start_factor=self.hparams["train_config"]["start_factor"], + end_factor=self.hparams["train_config"]["end_factor"], + total_iters=self.hparams["train_config"]["warmup_steps"] + ), + "interval": "step", + "name": "learning_rate" + } + step_scheduler = { + "scheduler": torch.optim.lr_scheduler.MultiStepLR( + optimizer, milestones = [7500,15000,22500], gamma = 0.1, last_epoch=-1 + ), + "interval": "step", + "frequency":1, + "name": "learning_rate", + } + scheduler = ChainedScheduler([linear_scheduler,step_scheduler],optimizer=optimizer) return scheduler class SingleSessionModule(Autoencoder_Models): From ff7e23522406ea60e70585918192c8cfea6f0117 Mon Sep 17 00:00:00 2001 From: Taiga Abe Date: Thu, 6 Mar 2025 15:07:19 -0800 Subject: [PATCH 24/34] arg order wrong for chainedscheduler --- src/behavioral_autoencoder/module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/behavioral_autoencoder/module.py b/src/behavioral_autoencoder/module.py index 3c72e2d..3f15666 100644 --- a/src/behavioral_autoencoder/module.py +++ b/src/behavioral_autoencoder/module.py @@ -93,7 +93,7 @@ def setup_scheduler(self,optimizer): "frequency":1, "name": "learning_rate", } - scheduler = ChainedScheduler([linear_scheduler,step_scheduler],optimizer=optimizer) + scheduler = ChainedScheduler(optimizer=optimizer,schedulers=[linear_scheduler,step_scheduler]) return scheduler class SingleSessionModule(Autoencoder_Models): From f7113eb5e3fb05cc22f1a1b01a66537d643a28a3 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Thu, 6 Mar 2025 23:35:27 +0000 Subject: [PATCH 25/34] step scheduler needs to be passed the raw functions, not dict --- src/behavioral_autoencoder/module.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/behavioral_autoencoder/module.py b/src/behavioral_autoencoder/module.py index 3f15666..a1c7182 100644 --- a/src/behavioral_autoencoder/module.py +++ b/src/behavioral_autoencoder/module.py @@ -76,24 +76,18 @@ def setup_scheduler(self,optimizer): "name": "learning_rate", } elif self.hparams["train_config"]["scheduler"] == "linear_step": - linear_scheduler = { - "scheduler": LinearLR( + linear_scheduler = LinearLR( optimizer,start_factor=self.hparams["train_config"]["start_factor"], end_factor=self.hparams["train_config"]["end_factor"], total_iters=self.hparams["train_config"]["warmup_steps"] - ), - "interval": "step", - "name": "learning_rate" - } - step_scheduler = { - "scheduler": torch.optim.lr_scheduler.MultiStepLR( + ) + step_scheduler = torch.optim.lr_scheduler.MultiStepLR( optimizer, milestones = [7500,15000,22500], gamma = 0.1, last_epoch=-1 - ), - "interval": "step", - "frequency":1, - "name": "learning_rate", - } - scheduler = ChainedScheduler(optimizer=optimizer,schedulers=[linear_scheduler,step_scheduler]) + scheduler = { + "scheduler":ChainedScheduler(optimizer=optimizer,schedulers=[linear_scheduler,step_scheduler]), + "interval": "epoch", + "frequency": 1, + "name": "learning_rate" return scheduler class SingleSessionModule(Autoencoder_Models): From f54ffc86fcefaeb9ac281495ebd3d4ecb3d8c659 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Fri, 7 Mar 2025 00:09:49 +0000 Subject: [PATCH 26/34] was scheduling on epochs, not steps! --- src/behavioral_autoencoder/module.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/behavioral_autoencoder/module.py b/src/behavioral_autoencoder/module.py index a1c7182..10d4e96 100644 --- a/src/behavioral_autoencoder/module.py +++ b/src/behavioral_autoencoder/module.py @@ -82,12 +82,13 @@ def setup_scheduler(self,optimizer): total_iters=self.hparams["train_config"]["warmup_steps"] ) step_scheduler = torch.optim.lr_scheduler.MultiStepLR( - optimizer, milestones = [7500,15000,22500], gamma = 0.1, last_epoch=-1 + optimizer, milestones = [7500,15000,22500], gamma = 0.1, last_epoch=-1) scheduler = { "scheduler":ChainedScheduler(optimizer=optimizer,schedulers=[linear_scheduler,step_scheduler]), - "interval": "epoch", + "interval": "step", "frequency": 1, "name": "learning_rate" + } return scheduler class SingleSessionModule(Autoencoder_Models): From 5103a0973a3d9e1256ef66fd47e2d0701444ce32 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Fri, 7 Mar 2025 06:12:11 +0000 Subject: [PATCH 27/34] added string matching to only create some frames. --- src/behavioral_autoencoder/data_utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/behavioral_autoencoder/data_utils.py b/src/behavioral_autoencoder/data_utils.py index 308b5d5..f0f6a1c 100644 --- a/src/behavioral_autoencoder/data_utils.py +++ b/src/behavioral_autoencoder/data_utils.py @@ -64,7 +64,7 @@ def check_exists(frame_dir: Path) -> bool: return response == "y" return True -def main(video_dir,frame_dir,video_suffix=".avi"): +def main(video_dir,frame_dir,video_suffix=".avi",match_str=None): """Given a directory of videos, write to a different directory with the following structure: 1. one subdirectory per video file, as well as a metadata file `annotations.txt`. 2. within each subdirectory, pngs per individual frames. @@ -76,12 +76,15 @@ def main(video_dir,frame_dir,video_suffix=".avi"): video_dir: directory containing video files. frame_dir: directory to write frames to. video_suffix (default=".avi"): suffix of video files to consider. + match_str: string to find within the video names to write only a subset. """ video_dir = Path(video_dir) frame_dir = Path(frame_dir) # 1. Get a directory which contains video files. Store video file names. video_files = os.listdir(video_dir) video_files = [f for f in video_files if f.endswith(video_suffix)] + if match_str is not None: + video_files = [f for f in video_files if match_str in f] # 2. Check that the directory we care about exists. If it doesn't create. If it does, ask user. video_files_write = [] for video_file in video_files: From 658e3ce3e01945da547bdcd0bdccabf140e45649 Mon Sep 17 00:00:00 2001 From: Taiga Abe Date: Wed, 19 Mar 2025 12:32:59 -0700 Subject: [PATCH 28/34] started building stub of eval --- scripts/eval_single_session.py | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 scripts/eval_single_session.py diff --git a/scripts/eval_single_session.py b/scripts/eval_single_session.py new file mode 100644 index 0000000..8465bb4 --- /dev/null +++ b/scripts/eval_single_session.py @@ -0,0 +1,5 @@ +""" +Evaluate a single session autoencoder. + +Assumes that we are given a path to a directory `preds/{modeltype}/date/time/`. Will use the configuration parameters stored there +""" From 8d861ae8cb73fdcfdc00ce90737a780aad3d60eb Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 19 Mar 2025 20:07:21 +0000 Subject: [PATCH 29/34] make sure we save data path. --- scripts/train_single_session.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/train_single_session.py b/scripts/train_single_session.py index e1f223a..279a50c 100644 --- a/scripts/train_single_session.py +++ b/scripts/train_single_session.py @@ -67,6 +67,7 @@ def main(model_config_path, train_config_path, data_path, data_config_path): train_config["val_subsample_offset"] ) + import pdb; pdb.set_trace() ## Set up logging and trainer print("\nSetting up logging and checkpoints...") date=datetime.datetime.now().strftime("%m-%d-%y") @@ -109,6 +110,7 @@ def main(model_config_path, train_config_path, data_path, data_config_path): with open(os.path.join(timestamp_pred,"model_config"),"w") as f: json.dump(hparams, f) with open(os.path.join(timestamp_pred,"data_config"),"w") as f: + data_config["data_path"] = data_path json.dump(data_config, f) print("\n=== Training Complete ===") From 9ea6498c619ba6b8315ac20000968fc68d04a1b6 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 19 Mar 2025 22:58:25 +0000 Subject: [PATCH 30/34] added sequence dataset (used for eval for now) --- src/behavioral_autoencoder/dataset.py | 113 ++++++++++++++++++++++++++ tests/test_dataset.py | 37 ++++++++- 2 files changed, 149 insertions(+), 1 deletion(-) diff --git a/src/behavioral_autoencoder/dataset.py b/src/behavioral_autoencoder/dataset.py index cb02b89..3c86d6e 100644 --- a/src/behavioral_autoencoder/dataset.py +++ b/src/behavioral_autoencoder/dataset.py @@ -9,6 +9,7 @@ import cv2 from PIL import Image from torchvision.io import read_image +import torch import torchvision.transforms.functional as F import os import json @@ -278,6 +279,118 @@ def crop_img_proportional(self,img): img = F.crop(img, y_top, x_left, y_bottom - y_top, x_right-x_left) return img +class SessionSequenceTorchvision(Dataset): + """Quite similar to SessionFramesTorchvision, but returns a whole sequence of frames instead of the single frame. + + Attributes + ---------- + base_folder : str + given by parameter at initialization + trial_folders : arraylike + sorted names of per-trial directories + extension : str + extension for frame files. + frame_dict : dict + dictionary with keys given by trial_folder names, and entries arraylikes of frames within each folder. + trial_lengths : list + number of frames within each trial dictionary + cumsum_n_trials : arraylike + cumulative index for frame index across all trials. + transform : any + None or transform function + frame_subset : any + The indices of frames which we will extract from each trial. If not given, will extract all frames for each trial in each batch. + """ + def __init__(self, base_folder, extension=".png", trial_pattern=None, transform = None, frame_subset=None): + """ + Parameters + ---------- + base_folder : string + path to the base folder which contains folders for each individual trial. + extension : string + file extension for frame files (default: ".png") + crop_info : dict + cropping information to be passed to `transform_image`, with one expected key, `h_coord`. + Crops out the image in the original space so that ~h_coord pixels to the right of the + image would be cropped following appropriate image transformation. + trial_pattern : string, optional + Regular expression pattern to match trial folders. If None, all directories are considered + trial folders. Example: r"^\d+_trial$" would match folders like "0_trial", "1_trial", etc. + frame_subset : any + Array like of filenames for frames to extract from each trial. If not given, extracts all frames fr each trial in a single batch. + + """ + self.base_folder = base_folder + self.extension = extension + self.frame_subset = frame_subset + + # Get all items in the base folder + all_items = os.listdir(base_folder) + + # Filter for directories only + self.trial_folders = [ + item for item in all_items + if os.path.isdir(os.path.join(base_folder, item)) + ] + + # Apply regex pattern if provided + if trial_pattern is not None: + pattern = re.compile(trial_pattern) + self.trial_folders = [ + folder for folder in self.trial_folders + if pattern.match(folder) + ] + + self.trial_folders = np.sort(self.trial_folders) + + self.frame_dict = {folder: np.sort(self.filter_frames(base_folder,folder)) for folder in self.trial_folders} + self.trial_lengths = [len(self.frame_dict[folder]) for folder in self.trial_folders] + self.cumsum_n_trials = np.cumsum(self.trial_lengths) + self.transform = transform + + def filter_frames(self,base_folder,folder): + """ + Given a trial folder, filters out extra files to return only those with a given extension, and matching a given frame subset names. . + """ + candidates = os.listdir(os.path.join(base_folder,folder)) + if self.frame_subset is None: + return [f for f in candidates if f.endswith(self.extension)] + else: + return [f for f in candidates if f.endswith(self.extension) and (f in self.frame_subset)] + + def __len__(self): + """ + Required method for pytorch datasets. + """ + return np.sum(self.trial_lengths) + + def __getitem__(self, idx, method = "searchsorted"): + """ + Get all frames which match a given + + Parameters + ---------- + idx: int + integer index into the data. + """ + ## get trial number + if method == "argmax": + trial_idx = np.argmax(self.cumsum_n_trials > idx) + elif method == "searchsorted": + trial_idx = np.searchsorted(self.cumsum_n_trials, idx, side='right') + + batch = [] + for frame in self.frame_dict[self.trial_folders[trial_idx]]: + image_path = os.path.join( + self.base_folder, + self.trial_folders[trial_idx], + frame) + img = Image.open(image_path) + if self.transform: + img = self.transform(img) + batch.append(img[None,:]) + return torch.cat(batch,dim=0) + class SessionFramesTorchvision(Dataset): """Essentially the same as SessionFramesDataset above, but factors out image transformations into a separate class. Assumes we have a dataset which is organized as a directory of directories, with one directory per trial. diff --git a/tests/test_dataset.py b/tests/test_dataset.py index fd263a5..e67bc32 100644 --- a/tests/test_dataset.py +++ b/tests/test_dataset.py @@ -2,7 +2,7 @@ Test functions within the dataset class. Uses test data inside `test_data` folder. """ -from behavioral_autoencoder.dataset import SessionFramesDataset,SessionFramesTorchvision,CropResizeProportion +from behavioral_autoencoder.dataset import SessionFramesDataset,SessionFramesTorchvision,SessionSequenceTorchvision,CropResizeProportion import pytest import numpy as np import time @@ -263,6 +263,41 @@ def test_getitem_performance(self, tmp_path): assert np.array_equal(result_argmax, result_searchsorted), \ f"Methods returned different results for index {idx}" +class Test_SessionSequenceTorchvision: + config_path = os.path.join(here,"..","configs","data_configs","alm_side.json") + def test_init(self, temp_hierarchical_folder): + """Test the SessionFramesTorchvision initialization and basic properties, including with default cropping given by CropResizeProportion with default. + + Tests: + 1. Dataset can be initialized + 2. Number of trials matches fixture + 3. Image dimensions and format are correct + 4. Dataset length matches expected total frames + """ + alm_cropping = CropResizeProportion(self.config_path) + len_sequence = 3 + dataset = SessionSequenceTorchvision(temp_hierarchical_folder,transform = alm_cropping,frame_subset = [f"frame_{i:06d}.png" for i in range(len_sequence)]) + + # Test number of trials + assert len(dataset.trial_folders) == 3, "Should have 3 trials by default" + + # Test image properties + first_image = dataset[0] + assert isinstance(first_image, torch.Tensor), "Dataset should return numpy arrays" + assert len(first_image.shape) == 4 + assert first_image.shape[0] == len_sequence, "Sequence dimension should be 0" + assert first_image.shape[1] == 1, "Images should be 2D grayscale (H,W)" + assert first_image.dtype == torch.float32, "Images should be float32" + + # Test dataset length + expected_length = 3 * len_sequence # n_trials * n_ims_per_trial + assert len(dataset) == expected_length, f"Dataset should have {expected_length} total frames" + + # Test all images are readable + for i in range(3): + img = dataset[i] + assert img is not None, f"Failed to load sequence at index {i}" + class Test_SessionFramesTorchvision: config_path = os.path.join(here,"..","configs","data_configs","alm_side.json") def test_init(self, temp_hierarchical_folder): From 9527a36e7843ef845e50fce0cc9dc04850ce48fe Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Wed, 19 Mar 2025 23:06:24 +0000 Subject: [PATCH 31/34] the parameter for the trainer set up should be fit, not train --- src/behavioral_autoencoder/dataloading.py | 26 +++++++++++++---------- tests/test_dataloading.py | 2 +- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/behavioral_autoencoder/dataloading.py b/src/behavioral_autoencoder/dataloading.py index 8b90f7d..5260d30 100644 --- a/src/behavioral_autoencoder/dataloading.py +++ b/src/behavioral_autoencoder/dataloading.py @@ -3,7 +3,7 @@ import numpy as np import torch from torch.utils.data import Subset,DataLoader -from behavioral_autoencoder.dataset import SessionFramesTorchvision,CropResizeProportion +from behavioral_autoencoder.dataset import SessionFramesTorchvision,SessionSequenceTorchvision,CropResizeProportion import pytorch_lightning as pl from joblib import Memory import os @@ -76,7 +76,7 @@ def __init__( whether we should calculate and subtract the mean of the training set. """ super().__init__() - self.data_path = dataset_config.pop("data_path") + self.data_path = dataset_config.pop("data_path") ## each dataset does not take this argument, so we remove. self.dataset_config = dataset_config self.batch_size = batch_size self.num_workers = num_workers @@ -109,15 +109,19 @@ def subtract_mean_image(self,x): return x-self.mean_image def setup(self,stage): - _ = self.dataset_config.pop("transform") - self.dataset = SessionFramesTorchvision(self.data_path,transform = self.transform,**self.dataset_config) - all_indices = np.arange(len(self.dataset)) - ## Subsample indices - train_inds = all_indices[self.train_subsample_offset::self.train_subsample_rate] - val_inds = all_indices[self.val_subsample_offset::self.val_subsample_rate] - #test_inds = [i for i in all_indices if not (i in train_inds)] - self.trainset = Subset(self.dataset,train_inds) - self.valset = Subset(self.dataset,val_inds) + if stage == "fit": + _ = self.dataset_config.pop("transform") + self.dataset = SessionFramesTorchvision(self.data_path,transform = self.transform,**self.dataset_config) + all_indices = np.arange(len(self.dataset)) + ## Subsample indices + train_inds = all_indices[self.train_subsample_offset::self.train_subsample_rate] + val_inds = all_indices[self.val_subsample_offset::self.val_subsample_rate] + #test_inds = [i for i in all_indices if not (i in train_inds)] + self.trainset = Subset(self.dataset,train_inds) + self.valset = Subset(self.dataset,val_inds) + if stage == "test": + ### the only way to access this right now is to pass setup explicitly right now. + self.dataset = SessionSequenceTorchvision(self.data_path,transform = self.transform,**self.dataset_config) def train_dataloader(self,shuffle=True): dataloader = DataLoader( diff --git a/tests/test_dataloading.py b/tests/test_dataloading.py index 928dbc2..ae07b80 100644 --- a/tests/test_dataloading.py +++ b/tests/test_dataloading.py @@ -120,7 +120,7 @@ def test_train_dataloader(self,tmp_path): "trial_pattern":None } sfdm = SessionFramesDataModule(data_config,10,2,10,1,10,1) - sfdm.setup("train") + sfdm.setup("fit") dataloader = sfdm.train_dataloader() for batch in dataloader: From 6ac055811c1ab2d80bcc065f115816363ac37be3 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Thu, 20 Mar 2025 18:34:12 +0000 Subject: [PATCH 32/34] added evaluation script that saves out results per trial --- scripts/eval_single_session.py | 93 ++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/scripts/eval_single_session.py b/scripts/eval_single_session.py index 8465bb4..da4f7b0 100644 --- a/scripts/eval_single_session.py +++ b/scripts/eval_single_session.py @@ -3,3 +3,96 @@ Assumes that we are given a path to a directory `preds/{modeltype}/date/time/`. Will use the configuration parameters stored there """ +import os +import datetime +from tqdm import tqdm +import json +import numpy as np +import fire +import torch +import pytorch_lightning as pl +from behavioral_autoencoder.module import SingleSessionModule +from behavioral_autoencoder.dataloading import SessionFramesDataModule +from behavioral_autoencoder.dataset import CropResizeProportion + +here = os.path.join(os.path.abspath(os.path.dirname(__file__))) + +def eval_trialwise(model,test_dataset,mean_image,path): + """ + """ + for i, image_sequence in tqdm(enumerate(test_dataset)): + reconstructs,latents = model(image_sequence[None,:].cuda()) + reconstructs_centered = reconstructs + mean_image.cuda() + folder = test_dataset.trial_folders[i] + savepath = os.path.join(path,folder) + try: + os.mkdir(savepath) + except FileExistsError: + pass + np.save(os.path.join(savepath,"reconstruct.npy"),reconstructs_centered.cpu().detach().numpy()) + np.save(os.path.join(savepath,"latents.npy"),latents.cpu().detach().numpy()) + +def main(data_path,data_config_path,eval_config_path): + """ + get a model path, and use it to load in a given model. + """ + saved_checkpoint_path = os.path.join(".","models","single_session_autoencoder","03-07-25","18_05_06","epoch=99-step=33100.ckpt") + data_dir = os.path.join("home","ubuntu","Data","CW35","2023_12_15","Frames") + metadata_dir = os.path.join(".","preds","single_session_autoencoder","03-07-25","18_05_06") + video_fps = 400 + delay_start_time = 2.5+1.3 ## pre-sample and sample time intervals. + delay_end_time = 2.5+1.3+3 ## delay is 3 seconds. + subsample = 10 + eval_batch_size=1 + + ## Load in data related stuff + + with open(data_config_path,"r") as f: + data_process_config = json.load(f) + + with open(eval_config_path,"r") as f: + eval_config = json.load(f) + + alm_cropping = CropResizeProportion(data_config_path) + data_config = { + "data_path":data_path, + "transform":alm_cropping, + "extension":data_process_config["extension"], + "trial_pattern":data_process_config["trial_pattern"], + "frame_subset":[f"frame_{i:06d}.png" for i in np.arange(int(delay_start_time*video_fps),int(delay_end_time*video_fps),subsample)] + } + + date = datetime.datetime.now().strftime("%m-%d-%y") + time = datetime.datetime.now().strftime("%H_%M_%S") + datestamp_eval = os.path.join(here,"eval",date) + timestamp_eval = os.path.join(here,"eval",date,time) + for path in [datestamp_eval,timestamp_eval]: + try: + os.mkdir(path) + except FileExistsError: + pass + + sfdm = SessionFramesDataModule( + data_config, + eval_config["batch_size"], + eval_config["num_workers"], + eval_config["subsample_rate"], + eval_config["subsample_offset"], + eval_config["val_subsample_rate"], + eval_config["val_subsample_offset"] + ) + + model = SingleSessionModule.load_from_checkpoint( + checkpoint_path=saved_checkpoint_path + ) + + sfdm.setup("test") + + eval_trialwise(model,sfdm.dataset,sfdm.mean_image,path) + + + import pdb; pdb.set_trace() + +if __name__ == "__main__": + fire.Fire(main) + From 665ddc004b4a9c3e241cecb57ea5e9d9eb03e89e Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Thu, 20 Mar 2025 18:35:26 +0000 Subject: [PATCH 33/34] moved things around so we have a preliminary version of eval loop --- src/behavioral_autoencoder/dataloading.py | 6 +++++- src/behavioral_autoencoder/dataset.py | 6 +----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/behavioral_autoencoder/dataloading.py b/src/behavioral_autoencoder/dataloading.py index 5260d30..b5e2246 100644 --- a/src/behavioral_autoencoder/dataloading.py +++ b/src/behavioral_autoencoder/dataloading.py @@ -14,7 +14,10 @@ def calculate_mean_image(data_path,dataset_config,subsample_rate,subsample_offse TODO: implement caching for this function. """ # 1. First construct the right training set indices: - dataset = SessionFramesTorchvision(data_path,**dataset_config) + sub_dataset = {} + for field in ["transform","extension","trial_pattern"]: + sub_dataset[field] = dataset_config[field] + dataset = SessionFramesTorchvision(data_path,**sub_dataset) all_indices = np.arange(len(dataset)) ## Subsample indices train_inds = all_indices[subsample_offset::subsample_rate] @@ -121,6 +124,7 @@ def setup(self,stage): self.valset = Subset(self.dataset,val_inds) if stage == "test": ### the only way to access this right now is to pass setup explicitly right now. + _ = self.dataset_config.pop("transform") self.dataset = SessionSequenceTorchvision(self.data_path,transform = self.transform,**self.dataset_config) def train_dataloader(self,shuffle=True): diff --git a/src/behavioral_autoencoder/dataset.py b/src/behavioral_autoencoder/dataset.py index 3c86d6e..27a3a99 100644 --- a/src/behavioral_autoencoder/dataset.py +++ b/src/behavioral_autoencoder/dataset.py @@ -364,7 +364,7 @@ def __len__(self): """ return np.sum(self.trial_lengths) - def __getitem__(self, idx, method = "searchsorted"): + def __getitem__(self, trial_idx, method = "searchsorted"): """ Get all frames which match a given @@ -374,10 +374,6 @@ def __getitem__(self, idx, method = "searchsorted"): integer index into the data. """ ## get trial number - if method == "argmax": - trial_idx = np.argmax(self.cumsum_n_trials > idx) - elif method == "searchsorted": - trial_idx = np.searchsorted(self.cumsum_n_trials, idx, side='right') batch = [] for frame in self.frame_dict[self.trial_folders[trial_idx]]: From 47d9b16a6ddd06654b5988e832baba45218c3bfb Mon Sep 17 00:00:00 2001 From: Taiga Abe Date: Thu, 20 Mar 2025 11:53:55 -0700 Subject: [PATCH 34/34] played with changes to training and added caching --- scripts/train_single_session.py | 11 +++--- src/behavioral_autoencoder/dataloading.py | 47 +++++++++++++++++++---- 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/scripts/train_single_session.py b/scripts/train_single_session.py index e1f223a..c2a539e 100644 --- a/scripts/train_single_session.py +++ b/scripts/train_single_session.py @@ -22,14 +22,14 @@ def main(model_config_path, train_config_path, data_path, data_config_path): """This main function takes as input four paths. These paths indicate the model configuration parameters, training configuration parameters, path to the data directory, and cropping configuration, respectively. By default we assume that we are training a single session autoencoder. """ print("\n=== Starting Single Session Autoencoder Training ===") - + ## Model setup print("\nLoading configurations...") with open(model_config_path,"r") as f: model_config = json.load(f) with open(train_config_path,"r") as f: train_config = json.load(f) - model_name = "single_session_autoencoder" + model_name = "single_session_autoencoder" print(f"Model config: {model_config}") print(f"Training config: {train_config}") @@ -51,11 +51,12 @@ def main(model_config_path, train_config_path, data_path, data_config_path): data_config = { "data_path":data_path, "transform":alm_cropping, + "data_config_path":data_config_path, "extension":data_process_config["extension"], "trial_pattern":data_process_config["trial_pattern"] } print(f"Data config: {data_config}") - + print("Initializing data module...") sfdm = SessionFramesDataModule( data_config, @@ -75,7 +76,7 @@ def main(model_config_path, train_config_path, data_path, data_config_path): timestamp_pred = os.path.join(here,"preds",model_name,date,time) print(f"Model will be saved to: {timestamp_model}") print(f"Predictions will be saved to: {timestamp_pred}") - + logger = TensorBoardLogger("tb_logs",name="test_single_session_auto",log_graph=True) checkpoint = ModelCheckpoint(monitor="mse/val", mode="min", save_last=True, dirpath=timestamp_model) lr_monitor = LearningRateMonitor(logging_interval='epoch') @@ -110,7 +111,7 @@ def main(model_config_path, train_config_path, data_path, data_config_path): json.dump(hparams, f) with open(os.path.join(timestamp_pred,"data_config"),"w") as f: json.dump(data_config, f) - + print("\n=== Training Complete ===") print(f"Results saved to: {timestamp_pred}") diff --git a/src/behavioral_autoencoder/dataloading.py b/src/behavioral_autoencoder/dataloading.py index 8b90f7d..20539f0 100644 --- a/src/behavioral_autoencoder/dataloading.py +++ b/src/behavioral_autoencoder/dataloading.py @@ -7,26 +7,57 @@ import pytorch_lightning as pl from joblib import Memory import os +import tempfile +from pathlib import Path -def calculate_mean_image(data_path,dataset_config,subsample_rate,subsample_offset,batch_size,num_workers): +# Set up cache location with multiple options for flexibility +def get_cache_dir(): + """Get cache directory with the following priority: + 1. BEHAVIORAL_AUTOENCODER_CACHE env variable if set + 2. Project's .cache directory if in development mode + 3. User's home directory under ~/.cache/behavioral_autoencoder + 4. System temp directory as fallback """ - Calculate mean image from given training set parameters. Gets parameters from SessionFramesDataModule - TODO: implement caching for this function. + # Option 1: Environment variable (highest priority) + if "BEHAVIORAL_AUTOENCODER_CACHE" in os.environ: + cache_dir = Path(os.environ["BEHAVIORAL_AUTOENCODER_CACHE"]) + + # Option 2: Project directory if it exists (development mode) + elif (Path(__file__).parent.parent.parent / ".cache").exists(): + cache_dir = Path(__file__).parent.parent.parent / ".cache" + + # Option 3: User's home directory + else: + home_dir = Path.home() + cache_dir = home_dir / ".cache" / "behavioral_autoencoder" + + # Create directory if it doesn't exist + os.makedirs(cache_dir, exist_ok=True) + return cache_dir + +# Initialize memory cache +memory = Memory(location=get_cache_dir(), verbose=1) + +@memory.cache +def calculate_mean_image(data_path, dataset_config, subsample_rate, subsample_offset, batch_size, num_workers): + """ + Calculate mean image from given training set parameters. + This function is cached to avoid redundant calculations. """ # 1. First construct the right training set indices: - dataset = SessionFramesTorchvision(data_path,**dataset_config) + dataset = SessionFramesTorchvision(data_path, **dataset_config) all_indices = np.arange(len(dataset)) ## Subsample indices train_inds = all_indices[subsample_offset::subsample_rate] - trainset = Subset(dataset,train_inds) + trainset = Subset(dataset, train_inds) ## use dataloader to compute sum image - trainloader = DataLoader(trainset,batch_size=batch_size,num_workers=num_workers) + trainloader = DataLoader(trainset, batch_size=batch_size, num_workers=num_workers) sum_im = torch.zeros(dataset[0].shape[1:]) ## should be for data in tqdm(trainloader): - sum_im+=data.sum(axis=0).sum(axis=0) ## sum across the batch and sequence dimensions. - mean=sum_im/len(trainset) + sum_im += data.sum(axis=0).sum(axis=0) ## sum across the batch and sequence dimensions. + mean = sum_im / len(trainset) return mean class SessionFramesDataModule(pl.LightningDataModule):