From cda5fde6e7cff4655c6341cf9b756a35f477c2f7 Mon Sep 17 00:00:00 2001
From: Kai Chen <chenkaidev@gmail.com>
Date: Mon, 12 Aug 2019 10:50:48 +0800
Subject: [PATCH] Add flops counter (#1127)

* add flops counter

* minor fix

* add forward_dummy() for most detectors

* add documentation for some tools
---
 GETTING_STARTED.md                          |  97 +++++
 demo/loss_curve.png                         | Bin 0 -> 37484 bytes
 mmdet/models/detectors/cascade_rcnn.py      |  31 ++
 mmdet/models/detectors/double_head_rcnn.py  |  24 ++
 mmdet/models/detectors/grid_rcnn.py         |  25 ++
 mmdet/models/detectors/htc.py               |  40 ++
 mmdet/models/detectors/mask_scoring_rcnn.py |   3 +
 mmdet/models/detectors/rpn.py               |   5 +
 mmdet/models/detectors/single_stage.py      |   5 +
 mmdet/models/detectors/two_stage.py         |  29 ++
 mmdet/utils/__init__.py                     |   3 +-
 mmdet/utils/flops_counter.py                | 421 ++++++++++++++++++++
 tools/get_flops.py                          |  52 +++
 13 files changed, 734 insertions(+), 1 deletion(-)
 create mode 100644 demo/loss_curve.png
 create mode 100644 mmdet/utils/flops_counter.py
 create mode 100644 tools/get_flops.py

diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md
index 0138a3f..4dc4a37 100644
--- a/GETTING_STARTED.md
+++ b/GETTING_STARTED.md
@@ -162,6 +162,103 @@ pytorch [launch utility](https://pytorch.org/docs/stable/distributed_deprecated.
 Usually it is slow if you do not have high speed networking like infiniband.
 
 
+## Useful tools
+
+### Analyze logs
+
+You can plot loss/mAP curves given a training log file. Run `pip install seaborn` first to install the dependency.
+
+![loss curve image](demo/loss_curve.png)
+
+```shell
+python tools/analyze_logs.py plot_curve [--keys ${KEYS}] [--title ${TITLE}] [--legend ${LEGEND}] [--backend ${BACKEND}] [--style ${STYLE}] [--out ${OUT_FILE}]
+```
+
+Examples:
+
+- Plot the classification loss of some run.
+
+```shell
+python tools/analyze_logs.py plot_curve log.json --keys loss_cls --legend loss_cls
+```
+
+- Plot the classification and regression loss of some run, and save the figure to a pdf.
+
+```shell
+python tools/analyze_logs.py plot_curve log.json --keys loss_cls loss_reg --out losses.pdf
+```
+
+- Compare the bbox mAP of two runs in the same figure.
+
+```shell
+python tools/analyze_logs.py plot_curve log1.json log2.json --keys bbox_mAP --legend run1 run2
+```
+
+You can also compute the average training speed.
+
+```shell
+python tools/analyze_logs.py cal_train_time ${CONFIG_FILE} [--include-outliers]
+```
+
+The output is expected to be like the following.
+
+```
+-----Analyze train time of work_dirs/some_exp/20190611_192040.log.json-----
+slowest epoch 11, average time is 1.2024
+fastest epoch 1, average time is 1.1909
+time std over epochs is 0.0028
+average iter time: 1.1959 s/iter
+
+```
+
+### Get the FLOPs and params (experimental)
+
+We provide a script adapted from [flops-counter.pytorch](https://github.com/sovrasov/flops-counter.pytorch) to compute the FLOPs and params of a given model.
+
+```shell
+python tools/get_flops.py ${CONFIG_FILE} [--shape ${INPUT_SHAPE}]
+```
+
+You will get the result like this.
+
+```
+==============================
+Input shape: (3, 1280, 800)
+Flops: 239.32 GMac
+Params: 37.74 M
+==============================
+```
+
+**Note**: This tool is still experimental and we do not guarantee that the number is correct. You may well use the result for simple comparisons, but double check it before you adopt it in technical reports or papers.
+
+(1) FLOPs are related to the input shape while parameters are not. The default input shape is (1, 3, 1280, 800).
+(2) Some operators are not counted into FLOPs like GN and custom operators.
+You can add support for new operators by modifying [`mmdet/utils/flops_counter.py`](mmdet/utils/flops_counter.py).
+(3) The FLOPs of two-stage detectors is dependent on the number of proposals.
+
+### Publish a model
+
+Before you upload a model to AWS, you may want to
+(1) convert model weights to CPU tensors, (2) delete the optimizer states and
+(3) compute the hash of the checkpoint file and append the hash id to the filename.
+
+```shell
+python tools/publish_model.py ${INPUT_FILENAME} ${OUTPUT_FILENAME}
+```
+
+E.g.,
+
+```shell
+python tools/publish_model.py work_dirs/faster_rcnn/latest.pth faster_rcnn_r50_fpn_1x_20190801.pth
+```
+
+The final output filename will be `faster_rcnn_r50_fpn_1x_20190801-{hash id}.pth`.
+
+### Test the robustness of detectors
+
+Please refer to [ROBUSTNESS_BENCHMARKING.md](ROBUSTNESS_BENCHMARKING.md).
+
+
 ## How-to
 
 ### Use my own datasets
diff --git a/demo/loss_curve.png b/demo/loss_curve.png
new file mode 100644
index 0000000000000000000000000000000000000000..02425551174d57ae6fecd51be7960acad84c934c
GIT binary patch
literal 37484
zcmagF1yqz@^e#Mzf+8V^NDUw$NJ-ZWA<{@FAt2q|LzjxuAs}5MC7sfx#Lz9>-Q9W5
z@Vo!D?)R;`)@3c$F!R3ip0oGa`+1(d&mrKgyd(}5DHa3*!I72{Q-nb7Kp_yc%=;MN
zlhLm{UEt-e!)s~f`{2jpzM(&OPl6CvLnzr8Bb@c@jUXo0HdaO~4hHr{M%E6dHi#Yc
z1|bN93L-5gqU@5mIprFwX_Rzxb)s&*;n`X5_lk%LO^lkI_&y)jXMHscNilpRzPK_W
zvF9b)C%u3OHa%S`(z_x<BKUXj@fy;9DZ;p8_*~j~U}~+-LZ^qnm-1hB`e0IeBtq41
zXvI-WQ#mPPy<lB<J#wdxBzeWvAN*jmITVpmQBzZY6-gBp6BFBi{r`UXOR<TuDfi|L
zaXu~h;{Kd2@mnpe@r^1UA%qJB78HEu2XQO}50}z@f%rdP$PW!*Vq%L;y$1rCBYzyH
z{#XZ+Qw?&l%ROvjJ3iC?ii?j=@-|L3J~Z_Gz*@5|ulRYu3}t#=9@fP1)}SZq$sD=@
zy=fU4XoFk~tc8C{Hm}Z~mXMPR^fuXXh*58qm2t5lR#!dG-H#a%d1}R<-M6d!oTpv=
zGBfG6YY*h*;ag>b)1@9~_SRKPM8gGIo&NYVm4^dw;&^SeQ^@G3sVTkUWJ!A+7+5Jk
z&rp(YE=x7s*HtIV&kr%=F!K=dPTAQUd^MoP+`8BvS#>;aadG(b_wV1ANbgHbPLrO+
zFz-tmo$LLWv%N6yu7h6TnHApv|F+a7HZGgFw<QhUy+SIm<*vA<M1F_!%byKbTQ($u
zZd^BKDK`%q_57f%7KL@Ko+ndCY5Jrx9h@il_~CdJXaUJK^=EYS$u9a82Bjvw%iX*)
z<o*?p@$uK3W3-MPiCDGyM!QGz-M7{Ms@Ct@_)G<Ra!{qFRaM2s$H!YE&CSjI#i(fs
zNb8uXsTKW)+JTkSuIyAw#un!0rq<TmPI=(ghzNquA|cvDV8oc79(f|x`lzQm9?1w;
z3p>)|%Z}FxW-tjS(MMljHzJnv2TZE<(sYr=r@P@E8yYbV^8ux6o12T}6JCD<pRhg-
zY9WCkUg(?;w0JCYrc7;ISWgVMB{{!bj#Aa8OOj`|p1fO7Id2{+u>Xr<r2hImchYfW
z%VYNshUJ9u-!B20?pq<)ltMpe8p5u)sZtkW4MOLtR+2ZIHZx+h=fOJ?|B49{VNY|Y
zYBJ2MDB&4cH3Hr#1k};-H?vxi|IeTL+tnN1mHQn#ySX}7xD(~-Wu|Crh<NzUCI27Y
zZy{vDRD_Iobk_!IMl0>mE2iDxOG{6N{{4%Pr8_xKE>;Wfe0c#A^~GL_)*yPPrPaOu
zZ)d?(P}Q;9kmGVyQg}A6RH)Z+WJ(N{v|M-c9u0FO{soB5e$(&W3A`)GBme&GO}p>B
z)7I{33#U=9w0#y55^}jnFN`+(`QgjiKbVx@@3RLhi4M4|4VN4WLT9vZqM1oz6^g^w
z;oJov1<y0foj6%*cgdZn9PuP61<^l(xcKCHAk^g0E7&wymDN8w+6Y33f3s*)*i<rv
zG^wVcxcE~OUP{+@hCv81yS`z{^vRC*&BLv&EoLn@VI!j@NHB$9=7@zs@r!V&FY2Yn
zE7R_mrw=YpK4HF1d5whc)HaDf)%x!4b<oX=zI)c|ovdM9^%Z2z`o@OC3No0aDdPE?
z%CqH!s+;Qz5+M)1n<Jf@jI|PK>N|~7tk#Cz<x<nrJN~K9Jr))gPBy0H5`-E{m^0-j
z`ZItd{)`qcjFzpTLwW>H?pW6y8NbhSiCYLuX$MBcajn@xdW0@Cy)GAW)k=-I=@Z!^
zBjsWIPqmK?x5l~2?V1-gXGbGTO6+_17MSaAu527G_c-~lPans^Pa*~B9I?xVzJ|te
znv}R5txqL4fK_v=Tj`gioVtky$@k2{8@=(*dR}SI@|VX#ttCdRyB$oOuCt%qt2Rq^
z?6<3)+Rg`1uADU7JO;}_&Gepm1wL~|cd(p<G>Y+b4l5+NfA=^WNBF$|ccS}tLy5;3
z$Mson!@)tyOp8^;RQo$gs3#R=zK+>(puc|`MKBpJ8q&_?WXjnd34@J~)w(KGybRWB
z@^v^KTd^OM6J7#Au(*eGzHF)~DTx?V5RSAcsQT2&+VB}k;ci&C12-F(%hRff@97B@
zsyOne$=uCg)#{pZzWE&R7;k~{=JZX<PB$hBDZ>ScIgOd=leqluJ+xlTub4);OVj&`
z8=HjVJMbXM$CjX{sQH##PyGckd~m)|$YHI?^9_UqZ?EtTHpmyt(W2xSZMRQ$3#~gx
zo(k|SC1n+rgC@%Bk5NoBhMm!{WY=YeX;%}YFpCQClqu(#Kk%Kr^wiX3Rc+U}>gw()
z9&+Zg&OBhc_T{eMNQui{zNY7qvvk&ASF9bm@YR~WCH#VOy&5-&h~-DN8JW0vOIM;R
zb&QVJJxIA#sbPE4(1^|RNB21lTG_i&ya%;s23HR3Cai$_pcQsYQ8Rz+$Xmlw+McLt
z{#zK=WoyfnBXiwv7w#V$6LECJ0n608BIImqRc7vzyn^FIbDUnqFY8^g8z?!g*T3T4
zV+RoJI!Zpv)GRxz+O1h=OM(%zLhwop2i@FU#=TBqiV2`$#M5<cd_X;SPiAf&Qn<`c
zYxl~fcribGI-R^igd=e4Y!O?qfNaj&^zMQcg@Z=I%vc-<V&6|2uCc9uehUBx5QdhN
z><h76I=*0NV~1v<ZHowcya#00r}QJWsZnM95k7u<?q3`z=83ExSUgdA5ntR+<r5G!
zQuR*^SJ{-;P<PaP#)d|JdhO@$zu6DsF9|Fm?)b&Xh>g+l=7M^S(@9Nj13=8KWKV%P
zTfQ)s3GaI(FzlvAN6eZhz>=>SRHA-H#C$m@+FeuuLkxcL@koK=<MaC?oSf{Na7K35
z6jJ_XXL%(8fUq~W;9cqA<r;GMr!uoJm?HFbP(Gpi>(k_3kSB}VNy0bCMAv1ruA~dQ
zmzA>zmuJ0UM`v}sR^x5F^?NGIJxMJw^#~!G+Px297SmoAEcKU54`boZL^?O;s-s1E
zS``Pq0tbXowOxd=678v1lHG|K&bkY$>#kAb1NvjFGEfjaa$fGa;-mB^O6}d+4En4Y
zKE#DhZUh2+EiJ*iI<&g_0}->D*t>V^&QnLinyyREr~eAQ{H!LJKa_n$z`B#1uM<IB
zw1!YV!g_*MgjmzEoUHt$VN;j1zmK)PZVrgb(`)DEYG$J9a-WG<*o*q+#2e54=%4Ct
z&CFH!{(fLmfqIv7{efprj=5UNqI^O9IhNN!g0Suc&rZV)#favCX#M%WLQb&ut}97V
z%$hC_mzI_i?ON4AvM1YkZqzV;Ks+4GqVzE%opjsi)^uB|h-1^!Z}1AcM+QSJYk2*y
z{Hd;otO7_a=c%TC$zI6pxP^`GbZzhMq(d(VDl;w~9yXI6O<cGo0VGW#q-SadK>VDa
zf^o-pGe|HoyM4;E_jK(6-`S!P+UfPlP2^H%%wCr8RXD5HUT2lX%lgxqGeAukjbYv%
z(3G1?5`l}&!9=%p`m=@`_iteyi)kK9OgiYgo1=Cp?|}6|18ABz>0W!eA2ey(bkkJs
zDVqcfap=7oxjL&p{`E=}lXUPIHHUv-^bKLn*;H+vPNp$wah&S*NL>Pt=SL%T*#sx$
zZ8cZddj`l*gTYKZ2;a%%!^)%K1oh&1qflWlI)~*hqk;4$G`@>X2Ox+F^}FN$CEEU#
zm4~e@YPzfirG>^PyIW51MA9o54Q7T+74Yx3K}WtvMLAncWXe&pxgRH5k5GCZVpF)S
z^74iH7^y>05VQN0wX2Cu7NqI3V6@ScyjP-r#z1RgXI|kzFmp?LU<r@qPl-FD>l&E~
znO8nxZZDN|)^}E|Xg3DXdnL}?Tq~e#2I4Sr$73a-E5Vrps3@RBop?8f^Pf?K8N}1E
zuyhX%4IN&%6E43}WWfc3$&(9+7INeq0Kj(W#;U2w!LnR1rSLOn0HAY;p2t;_h(U!a
zK=7`Ydy)<-j7&`YSBIQc%kgBDAn05+izUx5uXEqmCAlZI8>-cv3$})kHB4B*;P7k^
z8I^XA?YF!ywkoQ%ZjL8x*p0hJHKWo>OMS>}5_`p;dPbpWVx#bez1{F<uhnMm9USNf
z3@soOjJsls6A=UCM9h`pK*vP>wivO!IvQa!Y<mm5YzP6`ux)>-bF|v=VXkUJ??z!m
z{6)V`^`UjL_caN~jj<v<LDss%*I|7{^xmgUJ1)m#Sz~2pGytrr$Z-$idOZpzxKE}|
z9JD=;p72<zA?0|rB*BuWoQjoOJ=L^*R#;d5`C?n6W4SxQ1)%cYZWG?#S5^-O=NkoY
zH%?JM+=Wh^`Y$%#7g*%TrBFB#W#7qg!rx78c9q+-xR}E$G}@cjs5)QYiIC5VV_)F&
zIv;EqbviZ~a)c7Ge0Zfa$P*YC7{`Ag_Ojs;iwcfw&>jKLE}W5}dSuudY!EAalPT12
z!dq`g(s2IIGf$(!_gz|Rk?tRAMn*=bs^tl0Z4V~EbF%S>yAO#beS25Xb@vw;084f=
zYgK;%Q<niO5~H~vGD$T+pVH-YbvgeOPk(}^+{&Swd*q<o#w!G<C7^IAcftxEL7L!g
zS$15!err2K(?@?X6M0<k`{6OH%{&D(V?D4&$ii}@tQyw1a)9ULke;%+wqs8GR!M1m
z!}H!)-A|)FaZq0sQq`z3H#IK(c)1H$^V#<Gh4&wHyvd1;s<pu|qq)Bm2a`WPseS`R
zL*qXwMZij8CMlk{4l7<IBMzTlQT67xhoLya%oHU!uycSe>gedetRZn#`DOqslLyQp
zb^XM2wU1+!n)(d!P2u}@&oWSXfS8eJP-uFBAD&!Z=n^M$<f*E{e`<B({$aR2WZ(sg
zvdm^p-}?EtedLr2di(2(R5}i%L70t-EI``JC0UX&ik85PhLG_O*Gaz9yVz886tJ}e
zS;qKLbN3rw!`ol9C(ct(0UP-?kIcIao_MAD`;)UZvAtj|%q%M-K(=E$IV?idqw9K~
zgl+-y?3%3o!YeV!KHB{VpW#KOnST5Sth#Sc7GEi%Z*c%tj<5mGdc_n0tX$LpMAih^
zIsus&q4Sjl_P@!p6gYy|lmr3jRDh|iFZEM8pujWWyW7d(w^xAcV={gyHkA^3V!X<R
zttqDih`v1+T=<c{g<6h$K$v=l&K;nh3cqA%>qDD;;qX7)iLL^wRd5R%xaHRXs52r=
zz?{HSBnMSd7qZzzF)@-?u$^eYJ1iO0B-!@LaH~;;FQD{YY^5laPD9O2kC-kwiyc)O
zpDBa+4K@$H0&5#y%h=WTDOCnF!DN&Oz<#WXD!*w9frEbB-tMYelq%{z5d^%TdOLvK
zliNw<P>Et@KDaHf7nStD$jy$w`C@MkMLh?-4Eukk@k+@UWoQA)(AXb+C^J+6G5r@R
zUSQNd*zLS-V-A&<z;S=Ep|_IgR*w+-V%)BRdWtC02T!PNvdV$~d^L@Kr}jXt%6^HW
zuwkeE;&doWsO4~N5D0NxP>elpH{Ab7Hc43$g_{p}ggQb2lt>B!dv|$fJUD00hbvqb
z!zE)mjId%f?H{nPu+(YaoGyfw+RnXxS+mKJ>@xqrx^_<j^$Jj6s4!$k2SrEJ5#Hc2
zC)4h%D=FSWs1hoYPEP;&^6ajU2!+?_oOYr2LI^M4cI5)!MqVi~sI5^2a~Egtq=h4`
z4t7)X;3lf5t{t~nmC8`e`|vvuB==TjJ69aH1rd-j5``z^?wiHn0^?H-a|;XC<1rIp
zki0PW1tgBnml5SBSEHS+&*kMj<jB<O>gtI-0HZvXqkS8KCP|J&R#sM*6Af2n$t&UQ
zhLwO1PFs2F`-Ss`UX4AG=FUVdZ3>3raR&fp^%BDeBiimP0Cy=X7ee6xdrhhidf=Gz
zTgORy`lc4{|IIWbt)zsYv&0RX)XK~(H8V4F#2vtTmx=I&dJlX@079pbEXz!^5N}a1
zH}HnFg$vU?v*1&}?NraoMs&OzW_rUVDcVbnI_;5~BSvE-Mko{OrX!_j-R?f@EP>#L
zX6Jk$mjzSXIsUK)sXUzz0(hLuzy2#NR>%|9aJj@9fL!QG;KloxTTmScYMH&O9dF8y
zO7i?)-FNC@%my>rJ<kz5Hq%}6EkXQC-<9m=4BnDt{^zaXtUkL|5|h61@s6#E8F?k8
z$4Xi9Mr#8ZRohYL=jYZ@A#Ygg2%FeWcW$n2NXf{ub8_atE6LBf&s<D5dIWkdGTchp
zq&X<z6|`n#l6?ClfG%}pXR-XZ=xJ+1@=OCOs;@DNeJkh%c}Hnrm;oq308j&@g=mmZ
z0P4?AzPRY3sOFQ{ehyF!{{=MahW}(Y3MrEH7;lGC7xBf#jyw+~UYV-VFVX-@uE>VP
zgD7i>u(S+${D$9WiWIEO=eIvnX=>&s+V-is`-gNZB*ethsR&J1+j_J~be{YazfEIS
zEKY>3?mS0*j@0dY1(4Q;CWVcaIn=lPu|;VxR&{0<E-TI>IU|f3ZZO!r)K{XC<?b1o
zqYBjdf@-K4rvjWA`;^{-79>VT2K$+fZ8PkMZC?W};c5Xi#E$F~W>5ZMg|A>TvSzv}
zyWdqEEn*F@pe;35R}{HRKq-<U{gVk%ne3O9(GRcyLlzj0-T2&8gSMtxRr4wNE$A5j
zAVgsnx<HXE%~LM&-X^7GfCVIMD9DrDt?vC`Cx|DR4MNzkk<fCL&FKL&G#7<jZLb(d
z`_O9M!`^0~E<&DIudZgew<xXNejA0>ZeMVbN4DSj!GtknfyKRLjgI!c78CQgWeRo?
za6U1QZwwAJQ?F<?`*-tT!7vJjNM3<2JLMqmdeUv}VMF)0I>>>4(6a!LnT*dGRg~e4
z6Im4#LIZ-UQE-g$7PW8>(=;+@<Rk_1Z^;1t7r<^ap9z(<UG;SGuw$1Gw}^~pi06ck
zAqu>lJD_Lc5cA*n-|M}HVLN1PKY92u)a3&i3Ujf403@8kh=^Bya%LKq#cc#CI$$C2
zQ!rG?SX3w|hg!J*dm7Hn{~kYd8(msX2Azlj`Ont|HCZ5}{h~C&xlA>_-?fY&dHSEH
z8&RGP)?$Q;Bq+E33q;Wt`Y!<JaJe|>ds5x~J_3RNDn=4ElndfH;MLU4B!j+aw=Bf*
z5rowy*EhmO6B5WREP_us5H6Wf8`@|^-J2t~AkSe7KGPi~hO59V9o#bvibz4T)_#?#
zt50uRjU2m?0|)yy_DQ6`5W&fm6Xn>qg)W+MY--xma<gCVAX0#>wyUejY#!0sj;Q3p
zc#Gy2x(z`6_SR27`}$jWaIl+*H;qBUk(XfcArN@P<z~9KNsAqO2Nyau79Egna~n0Q
zCgnj^q_O`kI<b2ZnQhVA+aOW9M-Hp*RW4?E;)xnV5jBS8VB_F-b&zOiN{u(QJN)kA
zx1~C|WK`%&q0U#1C}RDj)EI@jvsn?9T_7xNS4WMgvj1C6Wz<v97KYlnPq}<hrZldq
zmUpfx`RV1LIFLgZ3yv@i1ckfQG@<n>8;dQcAg)FJ?PK2+r8bpb&Sus?OJ&hj<516j
z1wy`Y-+-^(+7DFQDz}l2`fOKL*f(<Bsf**k;Qx4g+n<-IqPKDONeT6&JH&OtYqrX$
z4gkp`VCrR2XKd)eM>)PR2MG{k{^XUPaiFpF6b7&kAD$g<2`;PKC<Bjbdk?F2F8u!0
zL-T61hRZjyrs@rF(6Q<tU);P`VGN~9B4lm2FHn?r{cojjZ`KEo!+BdXROs9kMGfx@
zG^>ysa5y|p*jorigul{4=ao{<344VuHqm%18(iwTv>EWuAZ?Y~x?IwrSMkmGWa%g9
zG=EO8sgFB5Sm88i#r8Vu)Uj`W9<v+MaQSt8eVt+?OOXK-occMrx$3np>`lHn2B2~R
zb=%Pe=BUm4UPoBDDr_LM6$Pe;6`(hVA_>q8PjTqs$NUJMdH@J{7gP!dM}^*X$Rj-A
zOExGJdU;&Y(6Z9`9#0~SVk)d#$hMBfM<|^Z@=9V5rC-jcv};rqvk7zG=j#vWsl%-~
z<cz!HTgYr``!@t{E?0#4mtzeoug^D7RBn>&O6NS|$s4+GsMCq}eawO6P%i`R`l`A3
zzs|^EO`UJ(Er+l5hbU@k$vHSU6rKzX5ATmOTt7kDosRW-wemKc$!*u2XoF^Q!L^yA
z@UGh$NH4j_vi~fr5ppctWa>TmRZy_(OJ)t)ouRPx7l6S)uiCwGv!oMap5-KIX@?!;
zDGv_EZPee6+tDGz-1##jBV%GOI+_Ty=iTZ|+P*y-Ee|nLuQGT{FQ1;B9iYtkb!JDn
z(U_92FovNNQf~BL=0lFh>*h}`K&4EhUT&$r9lKG@tWh3p5GGhY;vNd$<^wDoTr(JU
z0_sZ=4#PGPd_IVP4!#6zc%jlNo>Ynv1U}@vOdi?tRHsW6Pq3LLOyKYN(MA<8(*OS;
z`L7S3E*ISVeOmv@-5t(5?LtH8v5N+JIoP08Jr|^NIS;7$YW6IUoulpxZfD<riP{GQ
zu1TUDMt*+&q}Rn37dJP%<GOP6ORe|~?d-xrB$xAI3_gv7ZLi>oE$A~J04GNSHDT8;
z{&Wwp{|@>A!?Di_Q>&DuJNZf6e`*W8aTj-gsyjZR30v%8h#1*(R&9mTp^0j!_kNuO
zbJMI&Cid11FUSD?W)h5{q3_3T1-d8N?V)R{`2%_c(#9@^Mpsm2GN?YBN>M6$>B`H}
zZ%G^z1aA)9w2)L>|6Qm|HQ<G}k4KBp#y#6I9Y<k;H{)bqG1)|o>MeGpdov(5|CVF_
zmG&!JC_#+Tk-{GPOwoe86aD-e`O?xs@}wgohNqu+yUIKpg#9M`YpB-f`*Qc^|NhlQ
zbeUSB1im-%H=G$EWr4zp9|K!ap<6#0OxQj=2bkQ(u-Z2__N<Mz@o5M2Kpi98kjGT{
zi-(v}+|<-dKn)i^e$=Q6==?%nGnJbW$Q$3M=LB@*Pj{=;*YNI9z}&VQ^qsfrBJ2cn
z0<Ug``x-qKwB}2wenXVIrdIm)Ekr2prlze|q-;9n2`r+Q%%%Oug<eX1spvHpkn9nM
z50=F<KXck<tmsg%wAt_(7n)qichM;A2)~|pHs&nI@!$U;N1cC51?N7mF?B1aoD+^F
z=!80dwAswLjiYcjU6{~i^+ku!XvP5ASjfyBClPN!V8m6<4{w#Jw@)CxgAU$LQhw7Q
zEG+|$_*hZ)W&RMAG#)K(&eR!j&uHSfKT1S0vQT@W4gE3<#2fhsEEoc!d&cfW29qUN
z+x$KjoLh`i;+Q{YMg1x!`og!gr4d^k8Y4u#&Vv9SaT)GW!iw(HITgy1e0c2`PC3EF
zhGQCfTnjXs*m=PZ=eVy0GJX@BZxaPQ??TWf45g$$77;xF6(B#=J)N~(W?>1&>WKrd
z2}9EW9Tc-HLY{c6HXbS+kKBAj>=^J|zAqn0T|!m!xUzrzaC`R+3<=c2^XroFM(IS~
z+}3Bf%HRUCFvQsKO<@*#)%KGu9#GTTQ(=KEQmxb`tdS!(Azh8zgdD&FAL`z-4fNHM
zSa%mpf)38n-%^zQE7aZ5logg-64fVHDmg)(%1_S%vY?72Rt~^%B@L)Ap4kOTZt@^J
z*3t}!b+no#Ta|a6JWZ0x=uvF{;<RrLBiGhEw7G55HSQqdqa5|Z{`#`hFpv2D5DXQ+
zl3<=$>$dtxFVDhM8r=k1&X*SP)Rx0SKvh#;<jsm=LNXghu2h|f%H$$Q{0v*4hYydU
zSkg124^5ZYy^a=qXYl1-nJ9G{Hyp)IQLiN_t#-7f({JN*j1mj#Nm!H*$c(Rf@D>F(
znZAZ53OW~C79yGlUp~km0d!o_O9->C6->q%r3(mtL%sFwKR!)iKpejNVR0-&opviV
zuivhY##LOHOBUt2KVPW)?{S!4LtiEr64Q*;PaI5OMqpUJDMjhQW1rWOF^kO#kJV02
zt$S@KrOZiZw^M;t)YNrKtE&+tPUKFV7d3)ArPu?Z6IPXLl23(rcX3c*Aoc=*u=9x`
z4w!N-B=!w|NX~#%`HhkRlw&?qAANkGlN*^fNdedkxvztBndbwX$qt6tP~P-mgierP
z%zlb&u5ffMjKOBbVJj%{cjc7WEsfz>s_+D3Q`&>fEQ0YocDYFf`wSEx{_1hJ0CA{!
z<fJ}-@;_$*3Q!67_-m*&c9+;g3A(Y3F;)uUc=gAOXq9VS+1M<57Ppu}32>>ajGg9)
zSpMh8V6>So@tEL;%rIty@m<9Nugxis^0y#-Mc<?v&|B}l-Hblb<&6r}8p~|YEEmKR
zLZJfl*U%)s=V`?i`OUWHL@<wN5hdE}mqg%-5b$+?1`Mla%_>-SXNsw2A3#&OsHS@{
zYHm@lk@ATV;j!(wwXf7u3Qp9=3R$BSQF3mK3_Q{>Cu_j&ymwDlC#7J90`}5l_UMyk
zG^*hxl4@Xbt*`i4Vdr2;h!Y-}!5vNUiwKqH9E333kG&iiH4jj3BE^DYHkQklBXLI#
zOjkE2Aj)7)uhaYNAeYh7t}o!%G+Qp3HzV9rJ;K!`$zP1i!K@&_wR#^{wvQN7jM%a>
z{R6*+DYrgPZ2M;CG<jlf1>dp@IRP_7v$C3D>c#ySc^6)W+^`%pZ2e^~q4Grv_Ocj+
z@q*$2ckc5gm*zotJQO^AeH9ftJ^Dw8<<W8}_MLL+Mvk+yHwpe(N!C*gX2>naxuSe1
z=>>@4uaZDGCqw@RTl>^8+iX|J-YE~5;c*2-^)~=JpewXPf^m=&;gXD$^VTWdc!M%|
zpAhCY_dO)-QsF6q+SP@ic17L5T!q}cS&Ufg76ptv#&3F5hBmGp`o>t(u?mogh~nz1
zc;@>3MyjGYR`*(Kgt-5Mf8wXk9|~#4SP(7(r3G>2Nkn)-82}_u8l6FaRBHTTmK)Yu
ze<?dbKKKTR49NR`ehx?{iox)X%1Vl<YPESYzw|VeD*g)OwRYumn)|(4vF_Fq<@#b3
zBCzR=BQ*F0_c^mcjEv;5-AUhE4yqY;Pde4W85_M<ClKEz<8Y1XLEQ<N2M&{49pd8S
zK*zT*vDW7iUAq8m2I{_sei!9fFz*LB!oat#J%*lz;BxA8Ctf3#D0xgXHn!miz}7<r
zUtB1XdxE;Qx++PzwS}$3K+uVTk5mDkP9DQV=^k^#zp`5Efx;f2PmECa+33(#Ygftv
z_F!dV&VPc4yECp3AefCqb7%jCh0awJ+RQoISZDCZQ!jEp7&i{vj#BGLEvP#wK$Y*J
zD*xAJmQl*~#X-tV7dYN=w#?g5H4{S4EWc)&H~f!y?S%wma5%OzoKo$C_**`L@Qein
z1J}}{GP(FP28r+Y&aN5!j)<k=?dq&KKLGr}5&=8wj}-*|fCC#{74=6f6t0W-nX++z
zpT}sve)Fc2Vkb8@*Wl0Rd*t4idshRa9uwMYYy<3F%Ji)-qi{L<(Owy~A$JF;j7Pf0
z$~O85rS%~OjH1noQ=)!Wq+&I#P9We31OLDJ+D|%?j@|p(<MSUXAggz+ckIahC@*I*
zJuW6@z-9sa)$pu`y^N&(?}GOCun4`pV;tfD9@`R?z^!MCtKmi-%WZldhO+m1y)G6i
z3nVR=L-dHhcn}3>wIY-eTe9wZg)?n8egvS7p>@ZBW-AA(<i2OO7|w(8#?h4;ch86L
zI^Cp!O)_Vosk_1Jh%gHj+v!CO-WwQ3rkw56g7T#v#dUY@$CV>uHFHu7ajziihL&#x
z!dAsx;zGF^Co;@SZ`Hr!KrIq}o?UW*<nu8vOijzr$3fZwoxSZ;TU{N$yu7Tv;|lig
z&OjTg7l(JUPdJ}kOn&V}(WOh~y^l{{Af+UqMK;<-)J-cbR>Zzl5fkMT=`-0MIjLC6
zY%6aWz-GeP4QR?CQ73AfSOIWAVtFNG#6u8Z`%gvHt_g?tt%}OO8P*hM>owC!F*1pf
zzq%5z&*1@jhV!$>Ld0clChV!z{;E6`N-B#;^kFV>^%?S|ZWbsxXHH^v(;M59lGH?0
z@5K<z{QuXwLmq+CLY8ACUqR1{%el+3)POWoE}<118G70p3U&Xn)kBu$L4I5w<?_iM
zCV#{KyjRC3Jk(@ldsovIx|;LZQgyN7bo5kaT;J@nyxn<4HocssXWa^a9Uj>w)Roz0
zV~~JBf@~@0a!?1_#MAxi_rb*#x$9y$C=!^#cv{gc8m+}hiiw5ufXDRvpyPV5nijTv
zod9=!i8{XoPE8n{?#^9ZJit&p{6aaa?!^%m*@&e}3ukcK!tF?|rF9o!{RdQKOL)_p
zs=cmkNN_%Oah0IFq=2Ynao9&A#m$NrxTBL-19T9cGE)yAd&$So4;40pSnFf`DBS!d
zDO|rCZHzcfIga3mA;GCD3W%zf<CBL^jZYXK+`R)1d*Dx9GchrxRaVB*g!0{Km=U&3
zF{V=U(nop*HJ7o@s-txw2x*|LBehtNw3xnkq=)$XTfe-5v$5Ze5Yb0;z17-W`QBs^
z%Te}a;A8ioMd^b6^-=xE{(&pVNDS$eRs`6%B`GT}w+Gv8gF{2jL)l8|)d;f!_l){_
zia`NH&HV_aUd%?*HVkx{$Wgn2H4@lzics9Fd}`XL&Eq%l(da)>SrmUpJ03xvdj&~x
zh)UG-1o}&%tscA@c*t7&4g403IN%BFHd5@=fwL&b`6`%97D&s-y;!<@(%8J+1x+HN
znLcCnBo+Fv!(v7$_#iSqJjLHdmAJUyvH;%YHzluL3P79PB7`Ld258|`tHQdZ1m91<
zUhhoKY`Lg}hb>E;*xK!<lV;*F=~+mY+s|)tBp8jXph(jg3wqI5>ii`%lyisPu6c!h
za$Dplz-D%(xy{e|aDIbvHrV4S^bXLqr0MYSSV)bE#Yj)GY_ru2i_(De>-8UC4o%Zf
zThV4@42z2`Ql<74v*tmPq0Kn&=c9ugSx6pOXI^l}@k!M3*MuVFg*Y-lOEx$GJD86r
zJS;t)XDL8ss3Wyr<vmN(Z4V;DtE^(RG|k}7EFc4h@x4H7qW5BfHJqH^EYs;Nb4L^s
z$K5rq&5&G$uwkbmivbuEML_-}9h<Jn8m>wiHU%#j=TdGJ9}3~|$-!TSr<a~Q1gd-g
zRjL7{ixZ?5D<WG!z#a5uDj7C?M|Q2eFjldk#dN{4mM5%bk@E$fAMTXLO*ocwhPOMm
zaQT+L$-mjo&u5;QtXJ5QgHt56PpirS5!BB6HPln@UL!8S_0Y{7ttebx(Y?Tl`vsoZ
zqwm(Jl12xamIyCce4{b$j~f@bKPQ86#A1A@VWimm9!X|9uF8OXo_H%)adCnB|FYSk
z8^Mzkys+uNnvBOn6JFdbJf(_k4CXaqg03ZNG1tJXW$z)wvPU!ApUZIpyj!Fn{{2pq
zdF$;=I8}VdZt=u9Pb1etrC3B91rj<k*hLIx2ntHi#WUwGpxDq$|Eqh?zxxsm@KM})
zb1=6-MXf^n!;!6{Bf5WtoS7VAx-l}CfIK9bqXeU+;FepmyItealYDp|f6L)dj}P>3
zSY&C1^=Q9FqBEJhR<K97U%O4!J5Hcv9Ud5mFvYGtls;4LEK;Y5fkfPGD8Mj>&cHII
zZa%a4@9(>+*LP^9$G5nH)s(_UrnJ{w939{QzcGLz3&U9>#r6C$%v%I<ure{F@0eSf
z8Tz6Q!N29|b|xY^D#Zu{MI|MvMFoDRO)QycT{M6(v1!4W-{W^#;mO2f89hmXdk~jT
ziUM$d5}WqxhEgK$t%qt`WcC%O<|%2*)5&B@_pBn1y5GILnv&5j+|V^O`<O#bsR1Y8
zl2l5EKAqt3&wzrZvM*JfH>2l;II4z@xM$Er+e>Y6J<jCL{F4b<AiF4~Nn-IA=Y0ep
zM~y)33Tq1v&L3H)Sqvuj$N90v5_z^-+sU)*e$seJXdnMwZig<Ew~N|XFC~)Dtjea7
ze3;KFX$#vBkrdE4dvUD6C8-$9h_^gE<8Lx)jYhpyS%zTA1^O={E*V`qEgO5Sz<nSz
zfh-TzeffqOI((B#N$_6q=E&;34!e%lJF^(MZA0W=r>aRE+*fx6y3{!3$J78hD__lo
z=14c10P^3fO7r7%RAWG!NY81*eB~}ch6OZ-`V4`Qp(~qelGLW+np)NG$eFsTCvCo=
z$|H<fdu2@_-WuqyxRw?}C7rX~P_8EY8jAktYV;o>vC1Iq$%sy%lB?RW-I<Hxf<g-e
zG$9{RiJNhADI`Be49TIm6ss7)8XpsGSxkMd$$!H3Stuy*Ee`@gTawU$!-7*(%-tIN
zZmD+sx3Q$a7D8jamOrAS`}5uy-CBXeoS5(UF<)5S6l!=zXuq;?#S|w%s4MBzg;#1`
zMb4X~8)t9!s*|0Q-N@G7m9}9gg_2|=r5p)7Zk`R$2umC;jPzYbjYx+G@;et!aI=9!
zLFO`ds$?YPiiJnC6iN2+<zR^d+40V#Ff{+H8s|bTaV1&~T)=)dBvlUm-$ku75}&hL
z(Z)~WbLnSWW*A;H4mpMFfWtUwplcl_;7yVYO<(%Cr0WMfFKu}I`=cLN6>fXF?7b`$
z)G<*6nw;C8;Icf}Z3zqR5yTs(ixT&0Rr%_2n(wAP+9zWa9`>#GFBf)R!SjF4P^oc3
z>5?3wE@-b!Ge_{`;kYSdQ@K>2RXAb7#5|l4s)eptVZ(Eh;yupZ1DGgX7*$A$1}Hi5
zsqt8^{hDNFRSS5U*StO|<~D@fyUeJYhJ$X)PO<>|iNIp#%dqP-1qu<q@Z~d{{O^7q
zUoOW;ILnP9oHPl{{EoWWi7pLTR8ySzy2A-4sHM#(N*_jA>Gac#bBj9xxj$`ne}{Om
z|M#gH$HIJtocY9Y{f{`HP(U%gDrvpPHaV{+7;3_ZhaA}@o$kqvvtFsH`t%E4<IyAb
zT8OD8$4s0-GW12-^Z1`_F?HqCh6jh^#sp?DAP0xJC9BTMngTMQu$oqz;9Tr*J7;X-
zd$H~=IFw~#4pE`eri?W^K??--g5}~RgASh2fx6Vx0}Y3r5t#C-tG4J%Q=#rok2u=3
zcOINE+d_RWH+Lt8#`nL4QG4^yzb86VKRtBQ>}^7~9Hd%sBL>CcFRLJf1iFcn-yw9I
z-!UvRv6<%12eWoNmYvKe<|lVYxPiF))=^o8EM4HguCHF1Hv&P2DU(WrmzdL3Os2h6
z{sjhcx=%Cc?(92O91veaZ%9dB%Bf;GIIBa4hxUVUF^wg<^@vJ00h{$<Q7V58hgn}a
zNbyr<bUf2g+JH>*R%Kse#d#A;6@OLU3WuHjZjbHNIS(Tj7H{o;ZQjDA6dyHis<hop
zL9y1LwAzNAWl3%f8&7j@IXI$#y|!Wg)3Ed$ySL@&i2?1zgFM%}xsEJPlfEU{U-a8O
zA%|Bd4y$1`7;)4xw>eX~V&dIDm&K+0@Og$~j7Z-8JYorKLE0@%HpQxG1vlhwEXFyq
z1ZUs3vmd--o*8N#&wSD16=chiW)6hcA)pKowo)Hq=j8l5vt!RThyV4dsJw7JHxD*+
zGSBxO6h^asBcV?RFVdJs23xjI!HcV77bIeo@b^IlD)Ak|G%#JtNb^p4JHBXOF$=oM
z1qBNWdtfz2sx@UHyNMz<C`P_a-v7O2N91`eCB$v?0wU~cKINqX2T|ebW_2Jm16oji
zpgs{Wt1{(}(?cTQ%W+;EHwb+3!pXp?UD8jEVFtXlzW=H*a+d@J;9oR22x$60g<~0{
zS3CL28P&YY3A&PnYRfco5Ni`bK!*{(j0Kl6BH#3sC9?mC$S}8fHp4OcY()u;U_l~0
zfZtE$u|19Kub2ADuKqN>MCfC3a|AWaq|0s;s)EPXVQl*YnE97xD(H}#t1dSR&zgG5
z27Vw*lwTkgQGJfC?Z~frZSMu(QvGDkO6Ohw49zp~J#L)&mKKVB;#7)@+R2ng;nF;5
zmHmqL;^rWRokrtKcJm#u{C*2s!g|50cjQG6&SgW%wEm0@=IpA|88E))E08p!y0{kV
zOdqv*w^&hRy9Z~_#O{_~VmY25bXF0guCXMl$PoPVkn96M?^3WMU>YcwZwK1TL(X(@
zZ=-Nt7Av24>gY50eN`v~Cy<oH#Du;O!Z6fpElMlP53XfThs5vlTqg~UD(!k?R9+KI
zMugvee?yULKqJA3y+-{00;A*lm_=epsI?hOI86py=8}iI{_esrlj@rIwT9%@!xQho
z86FZ15`8swD!4ptf9MQ{1KJnaI}>jlCSy0d;@9;lmHbT5+uTF&lU*v<M}|S#XW}qr
zJddV}==!$$VSy&GYxmNZDEY&nY+sKx$4N&fkM-%sx~A>fk%eQe)=ay_?=8W4AG!Qu
z3imctT_m}I!2Y>A`BgWzG9&E|)@*c0#zCYrWOinsAi3~vmLcu?GBl>~SLU1TvBg%g
zdqmj1_jp6R7iQEEE=;``t#-z1Zt}rH1Qb{7!R3VSH%Kp||HKA90x)}bSm?{s7fq@T
zgeeutn8x~i4?NP(n)UucJP?-8lQAtS#9iDi=t0}gM7Fa)a9_kCI88DdU)uO|YeR!r
z1%Gi+$#)>Gt*<YRHrna_IWqxC3|wc_f5r7nBnk7N?$WS4jDTc)e8DBzPmj22XD&Kx
z*zSBN^_1CXFzZ-z1)w@EP7VT_ZtzYMmC%Akm1uTA%3qe8KR`O41?|1&7BdEQg_Jm3
z+60>bCQW}}+-74iw^5CxZ`6KOF|NTSzHvzJdzQS=5D?x0+~OW{G-+%G6QCmyESu<%
zL|<AMC;c&UeK;>HG=FwJ&R}em)Ih@!38Qx0vdhF~c?NndPM-A6bw%Mjf+c~VFafOz
z1^&C1kz(U|N*2NAJ+vF~KRxD;pQ@L#{H;0CV|-mdsm(F*yG0Q}$MNOySWcVpd)~dq
z!13#wdT}wsw+smKFSW1kY5&6NRsFoe`b_YWrZP2tvods)XY#FM`2d<)I16ataJP8T
z`Hhq4iL-IALlSdn_~?#EqeK$Mo@~Vy)3r6T8%z+Ze%HF+)ka&LLkDlQA@&tf?=v>A
z2+ouW)TvkO%p4}pFXjSRA{L2DrlAi+T<!1QK0O6ReWVIX6o>C-;<TQo3nm-Ck^D@Z
zB5vjWSp`S0N6782x5kth<HA0OHt-KY-0^gYQjfRS1JC~DItrXk^W=lW(DwfCR{8E%
zwTMcGT#J<t%rUjHkbF|&xWoCRHIOGFJ2}wA$Rsy*j-~3Go^OS|;XE)VpnYZFEW74R
z?2uJCqe5~2@}?=6M?c*lw%;l?8~2U{WmCmc#%98{;uffK0TeG(V1rJ&>2{7hXyC#K
z%sNDc211d)_8&n5xV$=l6myEWwi#PUe%{wDUn!{P{IjU{TrE?Cdcn3|Qt|+F#wc8V
zM|XPN@R-q0+<Pn!1FOJJy|u@sG5(mA<7LIQgO>FmXwskADgi@WuN4U-wWKoL%*%71
zD~p2k_!g?yS{|N=pvz`YX)L{?HU|qv@E|~=s-A`Q-Tvl{r2Rmg%O{T37!qu|_@WO0
z<#`PdDzIQfdH@R^M@%Iru&EdW)?*`+BITzSW8>kvV5<5J;?j*j3k3gsS*M1eDohPN
z@IfYfV!D9of*S=`C<qsVE)JIW#V+bu>wDj#n&JDd4eWor_M*Vn0m(=jceK9foA2j6
zmZLz3Q@3O9@O&}b_>c^as|rm5?W-TKrqjYgftxA5X`t!@Z8p~uwVx+Rm-HaaLJ%5P
z;?nfXuXd-P>$>0?!#<mscpnB`@zhH}B^X~Dud)-bfgN`baS&N~<-f-+su`i6F5Wy>
z<u^v~dtaZ+ng?FhYEdqK#mPrCV+b0$Gs}r@B3$FcmqdVA9vi^V5?BQj%>Q`TlPHbp
z^%oDiqofC+<k}q-!t9?6B(_G~>9$->t8*wJ6Mv<ntt=M+zgDTUgRVDUT<Y_II;==M
zc<(rxe<p`uCRUJtTm8G$(?`;w!Z_Yj2QGhPYG09no>87D%_kfM#r=5?Hmi76FLX$6
zOuiV3OWdw-sp(m%oGzEfWF%{!WO>{H_SQJ!B%Ap}rkkGSk50TNa9fG_J}lVXHvlQ6
zH#YfQTT&H5Bl#VuHd0t*;<zo|iY%pwbmM#(jW3&#+Jka4wldmReLN&e_=Ya(-+X&$
z|Fw7-E95P&E79vol+W`scxXNRZk0kuwnh47oKSxGHL_4AmnIQmuLZwYXu<p70AJq@
z@|zhiZXBy#ela3Y9FLqa8{I**7GER%WQ<o4le$lcG1{AiL^nrmj1?-)6lsj-zXvxz
z{5KmXBmgIa=~G@&v@@(klHW*AzPcwt=Npc-@I9p6shdw)z?)&oyFRur2NM)kr|a6y
z)*l|oM~sO^PM*dwh(+MibbN-K*4@p>#;6xa3qA;gws5B;cO99?K3gm${y<8#T1u>=
z(!9D)WwpokT+8A!fR5Fpnpk4@p!ts>Gd!S0@GCviWjvL7(ZIswb8bFnHsk)@>I6<1
za*)fh`$yI3h|MmtV#C40yH<VV&qc_bpqI&;7%TlZ?)oVW8_T9x6&6$$k^*v88kZmc
zhOM}kwe0H@13?h`0^#A$7v&d80z-$es!4+acns-;pWP&n%#OqG4NtNL=ZPBkAH%Qh
zcb}T@#=b$aD<uIgV^7n%u|LrSLMwKUuFDkt{YWexC|(KcJXw`|qX^2nCujQ_JY4OM
zGvGXq7CLP<?0H-oYq9J(iDbQrSl_h#yV^rLi}XW+do6!7+q@$vG7HUA+q}^My8(q>
zmdq^ugr!#-d|tBmAWbspT)(;W{?)g^kMyoLY%I<;E+#+^=c$mXkxEl&-(FkCc4r~H
z00|VKBh-%LLT$^Rj<bqUmUgZ0k$fKlG9?f)M|*QHiFx69<M&5H17?r*PAr`l3rb0s
zDYug4T|Vd#v|Lyhw8BK^I!Z#D>50~c5upk8gG`=^sAkY8Md9EUgFMDjSt%$U=Ybev
z#+T=}_*c9oEh5&L*YEqyo<!Eullfpo|95`plm=-L5NeMPjnHM<O!f+qp@#k=uiHz7
zyXvif`ZU$bf!@k%p&+K4%0P2i>|3qo+BF&}V@+Q*{A;6*M?w}2U3&%{4{SWc#-D^i
zbQ3Uah8%bOqK+<)k61v9p}7VlN$A{(M@(?|A5O_{nm=tNRE}E@hy;Fz=0m5LD00bY
z%32jY`Uuk4YZs6M2LjX#^cBJGTv5-GoocRrxFt)9n%qGiowjr)oSbJ_gk|BKJyBJ)
zv|jOv9@XU)6~omBPT_&)FFu&P*8#8xaNs06uUhzq_L@^Z^lw}9A4gk8y7{v=0m%r#
z0FVd;{DDKi9v6>~%waI@a@WeVzq$EJVv|9u>yUj#>~xh2i47=Vf7UK9lUxrwITw!C
z68*8q?Zw6|(`Pa_bBgE1W<s7SMq_NLQI1pp$ViOgcp}dtbl&wP&6S<kVSCdjOIrq~
zCfQ%u`$_XzFAg+3j3n;uW#V{L8QyE^yd$PG=J;A78q+K!!_~|4p+bE{NV%F*%#@_+
zaHNxFxp)NV?(+k|P>!Ijz8ADC6(KoOu(HBxHUDR1U9GqZ2MBSBtChjYt9c3X5t?Kt
zQ*3%yb2+DxNyFA|71<B^W<BR;Ol2*Kd8<ZdCt8lpoURX`-X1l{yz8*jvl2P?hRT%Z
zoG<qdQ=xfci;XW3<aZJSALyPr^gdQ%g}_G-UhCkeI(z$oN}qu;Y=yO*qT(_g+4GFs
z>7{WV1ALlBQNer>3BgM-vL83a<LnQtJ9+{(Ep0oHpy{PH*L^x7jMk{U;u#pjs{7&8
z)t2-7^#P`ul|}JijUpJY(qRWRM7HS%pYC3FSQx!n;3MfwE&+_jZvzl7-an%pk#nDD
z(*nsE^Z6DyHu%R*aHCQ;S1j+-SKS052T0!x0_{if=CK^Vm=Xz4dZbO#+UHd8)8DC_
z(gu8E`HFje2^y{*Dl|80gIyi@NtO?@M$dZ&x$<1&_A^ya6VL7dzb0$U{B_vSfS`b@
zyx?VYXn|QRh{pLEOUsdjP_g($FKp8(u(`FAf%n0kT&I*p#>zcWmFM@-g!_>S$3jHc
z=2gw3@V^;-cK25WJk2Ur0JI+c@_*$d5v0V0-6TDGk4ZWtAwkgk%ntpD?s#lATey<f
zXR<shw|P8=lkq*eJX>f~SQkyvF?(W(UF_cbE>oHoR#r$aNKW0a6}stvPs?kAl>`#|
z;3rDwVV%Vj!*dh+)X5E8WUxyfNw9J3FC4N&cfbC^0^<8bV^kZ9CX#BkmEYzAg9vX2
zTVa&m`|lmplp+(f5nYcC)zhIdwj81s%;IGh{Kq|Fz75VS8A5qn9~WFfzr#7W-89Ik
z20p%EMq2Z_ePgGvMXn`#f1JdC%kH1S&VFQ8$&_TYneFK}{dM@`%4B4G=KW|i91GP~
zB^SEGOJwfXvlTnaYi);3`X->gcR6=svQxXT$+&TOBXo2Ob9R&FXiq=Ecqa9L8OrSc
zllkX-$;<oO*NEcO0wbd*E!yw39tLrtG2mzT5lP*DkZxiO{=au{rDyACXok|nKJuBP
z^Ap0Ii=%n*<-`?N`M86QgZ7Gg9(G@&t?9cK?gYV0+yl=SUU1%cm}ZK<D+~-}RLLPv
z%2{<`vh|Nzh`mo8_s}~aALG@dw!%|Oqx!ATA0w>P4n1LP<|}$NpJP7_cPJM>LY@rp
zzEG%x6cZz{<X?AfOBS)DcX>5;d9B=3p^32s<zDFRGYN-*7vxpiX}G=5XaQBkeW8}K
zkDDg-|8o}LkW`Q^P(`8(FZ5>9vg@<@>plm6H{5rf)L1<4p1sM}S%Myro~?GYL)6N*
zxDq>~*{!k`tjg05=BMvQym)j_wW<(f8sx(0X&7-YL`-DGYU>>YtGa#Lg`$Fpo{EHc
z=?;!e@=xnIl7?Ql1{ulxluw8NEgJs%OQig|kM%eP#ZUDK&N=Rzm2uC*te{Qe9*#to
zgV|^i<bWPu!hXY*-pV^~@p!$|q>p6Ad>VA`V%4~|lI743Gh~97e#@Ah^F}ECt5eL7
ziCTEO#c{#wiy!ApVj5>FTd0Hm^D{|Y$ESQ58_m$I{Cg9BF9t^N4+TEI`@yxq@ww*a
zBT2k+yEZ;^Nc@EA`{BoB%%7uP*{HXV`ti(-?OO~bBi~~1>Sl|`w!eAnl!vPW&%&X=
zC|<3^G8Rje(`J+D;r`A1dW@mtNl~fCd>K)#=tjSWyyVyoIejNhUWkIpi-o%Z*Q-?&
zvSiisuF++_{%6VsABH>1K2Qp4)P&(p)`vrz_<w4D#S|_{tnl2q2az}+#fQtu<VdnU
zH4Wc~M9GygEE>DzhdEwEEOI?=D#<>{m8xRDbKLw>@7~mGmc@zQ2ObmIpgnt=z#>Tz
zUY7lMSGA@ZQP*mwo>!U8mzh*=S~*=pn{CTwQ@=G`fJjO7pn*jG1V=*TuqhQc7DLkW
zR9T=ZIm-MzDsPZ8q=7M;{KAR0ZFzA4Byq4gwe7>gPgGR&&I|5K7o*#3(e%C~mLiR<
z*yYcRP7EIQ&>LdVt1M$J5#E<4aioo!|LbkrGeJ~NN=2M)P=YLmi4jUJ-!~?aP2%Ss
zI3*>y+i45iFee#!{iua`mJxSh+ODD$Q4(}7nKt2k;5$yx$2ZFvp3(w=Qe9d0onYhC
zwo{WYE1AU6>zED;M8%#06a9kH^U1*Gu|b1M!|*7jpF=@r=E{$im>$svF=I*HY4%{O
zNQN3*kD6m}K6k=3F)Q3tq(m$XD-FG=da(0ShsV|M;{Jqh$)($sS1T4Q`=@*#ixRo>
z&1iXdX$?}X&dT(4#$c`SuChsD*mP-?{btOD8xyyvqIi;DIW~JD6AiKWV)0g#-M#Kb
zANt?n%&VrEEpIasvY*hEhXpAlnTnh`Bc~37P4YlOudyVjDuI~1Ayet!tMIp-Z#Z5w
z%I)7E()FES|H+w%Tlq6^3+ovXvdANZaK-am^OIhEj1NTu+--LBeTQ%N>AgwQtcA)+
zX^C>PBoI97=-um(yMlhYgvI!ef(%A~e&#44Nlm1C_D{d+uYU`BbHroLXXfhTcQ6-d
zNd@Iq2GN(s1Q)IhaH+6%Z+a3t)CUc|2x38ali<)4#=W`757`cJV#<#-SQTlO`Acbh
zXPzJUIQP>0z<C^OL#xHn_8ohf8^a8{`o0>&`qJeWxiO4sp^8r@FU^hBHHg&-+brm`
z#dFe6mboKs3dg$3|1gu`wR<tF5;cu+SgYsdogq|f=VjL3JaMgc7_uX|_q-3czCm-W
zxlrai&cms)s#B8<*BmCVp_zQEiOJL1<Fw?PX7mZhucJaoea8>}u0T#|VU5dwuxljj
zL5}mb{6(dA14Yiyv<~*-DSYG+f$4M4{=(u(J!XY;f?qimdCm5cufDe>9q-7$%1PIi
z$WqWmk~O{ekw0@%ARH-}nbSES`HNRK6NfHl11C{EF}HNSn@Wt_AD+`Vacq7~&OiAX
zn&kU^+=uNY?2El`tog3f%=?1Fx-Qiqow<QC98H0&R7Js~xA7iD)W74Gi~Kk<TfRwD
z%nd&NB7nu-@K9v$QMBXTG^wNy%N9?&f3|4-<leip4Rxj_l8h_hPdKDkmCHyQ4zo04
zF4Z<#*cReB%&hc2pc=IBoJ4FRUg}JC;bc{$`D6OH1yxIDT7G-63gB&1j&PJa!r&iX
zqIJW)ZYuR5vmh6#(1#(0A||E+A03&Ko<Euq%lW~{&8%nciru-4WIcS#L*8$c{PAf1
z7{^nf)++W>SAXvB;gd-uWvhE1wn=A{8fw9`+V5gj8%5kp!mPk9_I#n?Sg4$>xD=~w
z0LvkN+NlvlVp@<s_u<+YLZB)TByh^{bJ@_wG_v<^%^W;7uu#!s^R4B-undexR*y$o
zDW9#%PkzZiE&9!DjKG+d6aKr;OGC6k1xqnG-*TSs#?jxZed>w`GFU5Oxyust!u>Rc
zgovWXf26Cgzt-ndtHUaJ_a~#zE`8&65ku|&i?^?SilYg)#)7-M6Wrb1-QC^Y-Q7L7
zhY$h;ch`lW3GPmCcfQGc>-!IG-Jf=AYink@dmcN_={`S+j7>d$3txxs*S7uHj8o;$
z&h>OOS#1*#!Z*NnLWG)ah+xR@k9G8iqFRnEK70zh!sO<9$8TlJzJF9-fTM-j7wvg^
zd09RY#3RVr#LT~3i=#|iMAVQaZ1u=VIG9*?(llBwvNAGHIc<|HDdr`3DgT;%xe`o4
ziY1H9Y)PYjK;hjzg48HyU?DKkJz!SKMBvXbtx`F3(>eMTr41Ddy!S+)krF;3p$dNS
z*%0Hr+WLFnmvf$P0gt`>4y#hAP)@2r69uuuBJ6;Bk!^LMN0(eDPhG6O>xA{k1ZKQn
z0ZtRUCbayY)BUU|FP*t-=KGgU43hMrg&F*xZx~bu+ynigT;s;yn2beTJ}`#g7dm`v
zXyvb{Vbvwg1)+?zIvaWkUv&4BKJfKG)iIGg`?ff#U6a+|W={mH;k7hn`e+lhYy9T}
zzwc1PR4LWgqN#gHbmcLEcFY%^ER-B?R9|*w$IA5k*e(S>s5a<p`!6}Q&ZTGAVj@+0
z0{RtLKt2$h5}F7omWbX+W<fDGtNC+hW~@mjnB!5_H-+O$`4avgZvku1)?f}$BiHz4
zyou0ck#iqr`+P*rLMt%ZyA9WwbX6q1!Sp0Le<mhqRS!5n(V3&@z<-t$c)S;mWo^q%
zrboHV{mozRnAg1eP~Y`uhl2hynkwVW^{bm9vw5_|&&Lt}^#vcS!eioH|2q2Kpw;KM
zQ#^W7c!=uKq&Gg$n6)<lGAD1>f)$tfD%WwE>M^VVmjb$EBJf6&$3u*lj)-(fj4_fN
z82MTT$Kl))8Y%B7CpxYQEc5z_nZvMGn0FMs8?1e-^brJ2(P_TVeFgHb(F%@TwS*4e
z%%a3+NtlndHq7q3g>du3SHawypq=vl9^_RbybtC$Mwy(F@p9&8XSo}pn0@;`_$HMv
zZ$Y;Q>8@vi3D+6!EGGxr2YLNkd$UtZc3`ya1gg`C)@?iuq+x~9^@<U0_`E$xA!CAv
z7(EYWdlJ;m%K((AJ0@NSMw-PQ^F$U_BOFqE*kE9~XF)AL#FVf_N$&mKZ1}y1@Ovd)
zjLexen>ZdgV{+}|5+(CNzpSx|s>0sFJ-&u)@%l=9$Ajx3{xbNl0#(q|PI69_^Jn!1
z9a0Z<E(Sdc;+~ry?Ex;n^zy5OIO%G*rMW!KL0HPRP-OgloNLpP?1LT)&u|F3<QBQ>
zsHCM|jw%GE&y-Xa6p%AMcxFuMMyk?iGJIW~^SiuQBE3k}&723SeM|GWBE=)ZOqaXX
z@+j5k#u1Dx1f^A5X$Y_@T<N8jT^NW6?yDLc@+?HDb!T)b@3!VK1X|po2Ak>~^3G=k
zm>-CH&Hk3;L@%%|b7kzW+Iuy6vbZPU73bcYddeq_6#9f(cYHjQ?gp=n9)blgv0nG~
zSY^^VWBrQ-uB`aj=AB~HLZbJCS1v)r)~thPI_I!ibxEhWo4C#@IKy8HqS4&(hnCZJ
z;aN^_lyjV=D#>uECTA@6hL{{wrDl+=l5A?L%9;zKbmT~}rC7q#Spt#DnF2#@KYd8v
zC)80KeF|U>LINwvmpU^HU+ILVZHCdmqp0$lC%ZS7TxkeEl3DJFdl|dt{&$UxbI}PD
zv&X!vujtm3xdYN){GVQ{O#$yNCNWut*qyM_jsL-3R5E4Qcl(F+_i1H@yo>!A$>Wai
zd@lA4IO^3mAB$9Hq2E)tr}ABcy@~Fh$J?MFiweYBJP#IOBLvwt1**U+PKTNzFbT2^
zck(WB1}E-mV#A_34pAn5c3f4XtJY1hISMX9|Ctl~GWyfe3+Ye>$CFZ&^i(xPGSp*s
zEA?#f`)YQ%KZkUO#U+Y&kDTl+8Nrj>)=}t=mE|D0>J0GrxDLr54s2DdeaH8HU*rp3
zUx$s(HwA_cwJt2C&RIKlF&h1mUHa(EbhwOt_Ds&w2uS#F*YKxp*RJG*xz}1f#j-a6
zBfJJO05e`Xy92oFTxD!dKlEXWLjF|Fo?9Dhs=oW+fz<DEn%`b)Rk4UPH+V)Gt$=t8
zQjedlhYGn)@J{95wQ3r-=06lvjceTWju2uegO9J1bQUD?J;GuL#!$0M-t~i197#};
zw%3hSQ6R<M!R2IcSXWIkAnlyhS046~PGJlDb(TYwv8|Y4WgO~%*bM_3fK>a6S9_=7
zux+9sbC6F`%FQ4(fkb)`4XmOM7M%Xw_HkFy%ED(LQ-@6#$J>Ztt4D6Ym1amXi>gww
zM^^t;6&s7k^krVeurIRa2E30JDfpohT<t2T(l1_ujsLTNf8!|WfjXKTE!6t*ka%3I
z^i0m?)?kYxkV>VFda+I&QSN2wlAoHzE{z*F$60rb@*Mv;eEj|V<}rE$wZ=x4ET%rD
z`19+{*uANc*9G>Eu0RYh@mNaTcIeK0Q*mebUI*43TGhFNDM8&C*=$6PK{LJD?8eGb
zSUBG<t0)OkbK}xJjDj+UBgIrkWsml~o$e!kt()B6zu0@)xK$e<mRbbKBHPOI3>wNK
z@$Q1W5(zPx?K?)>u1e0j7Ixd5;GacjUAs88HD*mn4Zp#Htv4<4=2PjjJx-K$Ch_8s
zYIDhC-|i;Wumly@Ie!7lRlo%ELTVprrn*kQ9^O@^1KYOFV-BzBh2sBs@wJ?m@I24~
zb~bsbF*$?z-E}c><%Pq{6spYXb5@<xKmdqHa)!<1WT`aHh(CF4wI`zU#(QZ(bXN2)
zyj&yG==E=N1oD<sFTb@hVipkK7cUogRVf{YlTETxf4(skVVYj~TnLWtu*K=bn9~cL
z81gAQuZPzN5bv7kLy>&ohGm(@t;^dlDJsagZ&2o54r-hD+8twmR=ww9TefE7JP>D~
zDmV!8OI>MF2Z>K2UGCphUX5$2qfOYJOp+bYK?eBT+J8rFWtWw`vN)s>(j@N<#;7(3
z4UzwRnwpD#hd!NIO@PADz1>8sP<OXoCeyDOttE3amSh@K9+aF%?3ekkrc(fY2CNFp
z(*v|qIa6k!Q$!|_WTl<zF<#7UxEFm<7){C3KouJ%C7}HonCOx9WR<li7^xA0%QJk}
zxMuJ@PImzt&$Y0Uy!_qeVNUwbv%WD$JfsX>$&0YOq<lb;!+Sx^Omy=ikL_XiW+w@D
zO9{W$Z5!gS{M7={kznH5&bWi^u(ggjVHEJl$2)L(d<p2y!ftdgc9n{v{=4+UTGiLt
z5}F<wzPo5payP;rYpl|PF;gR73#46Vg~@X)6IR-f6sb)YehsQYJ{mm<sa4{XSz%Y!
z1r4mZjZy(wq0|J=*^&CiQ@Fa+2A8PXjNZmby-xI5&1k|DP$>f04b7C_MFbBPt8f^q
zO<zqM7DBB31<BQIZ^*YQR!BzJ{RDOGau)b7iEd*{yFLlV<WNU#1M!JIz8`V{dCN%P
zW@7ja8N~um7h^>M6lJLu88`-tjnaqWQ{h0}lhE<hb}t|{WiQ;9A@KO;oxi8Y8kGU5
zA-{5ia0hxfyNl<Tuhb$2mm;GXJzigI&302Jy(d}SH`9uCa6ykmJeN(iNL}tDu7WXN
zuq-P@$8F7|vR|e}p)AEBvaLY5jYJCj6B|{yhpIHTWLE{*!Vb%gy#xNffNI@<j*<0K
zIc$2V(Fp_-)6=q_Q(G{7(fhj8ih*z36*2m!0&1_spN=&0V`ni*kclT=iF-%HQ|cTH
z2j0O&wg*n1?>6*(Kd!o>$l*?7`SDaL)N+5CUau8GrC#APNK<Go<X~QDtx4skc9$b4
zEp97}_uWjOED=pN?x*mi0S;D^f#QqlY&IRbaCzv~tcripkJl#=$rT`%$*Kl&nc%<}
zHUL1<d{TARNl@iS=|1zAl!0j>B};Y?bry=ePLQWo_^q#{rW3%}<GGpRvWD0~Zgrm?
z^iuHZcLkZbn~RWd#2Fb$Qd@or?+30uy*rjV&H=Tq6PFIpknGgo?9XY&D|tfnKz{Uz
z!MhwihAzjlht(IOw1PKsW&$r^%wunsaRGNQVhz5g@{#@=1B7J}<c|<WA5y%3>G+7O
z))Bq)in6QkjR3*rLk^^@Dp5w2_tfgD(0^1f8m?XfaNn^@#jr2N=wCZuxbs(7jONyB
z-Fy7$FKS7)z9->^=dx8=x=+!jqkaMm(#i?bLKzhtcia{#QsD5pgO@RFh)NzeOVNm3
zl?q%GM3I%|Gf8+u{ux9q$wP{F@#6#O4jcz4zC4l-+JkNp@@H+(XprVmJ%C~ebP$Rt
z+Ir(8e0Pc56*sg_@Z$U7$gDo6h*=}Ua7UNgH6Jkj(-GY_F-O^xt{9)nsw;qkYS1m|
z1?8TcjUi8eB=0_So~X^8QdxgN9gT=c-$_)?<9aKLjLi~q@q7~lFEI8?i8bBLj<2Jb
z0_ZTPH@)clI?O@CI)fQmlHfbnFZM+R*cCy10A$Eh_9XE($fOHS)R2_U>{S|JBpZKt
z?T=l?9sQmw8nl1dKUXiPdV8RboM-oEQjCVGVL$xS&>YTd+D>UQ#AQkP3f-uq_DnT}
zn@m@9@jeYh8$RvYfa0No_et_2zD&;pYewjuOf1uP5e&5;SPCu}+GZU6D>7^;7w?T^
zJe!h(UhHDTt_Y)ESW6QkVs`mHMv6G{u!?XR)2BQZ@$`aUUG3mQ12kf+=Q<v1N{xIJ
zTm<@Rs?Wv)YT>ZMtf*81aT1P{3C1)4T$#UfzP&!!%4Q*Ls}eVvB+}HD-4YYM?Eoha
za=&7*p3>``@c)9L;A|^~`Dr35a&v+;+eu6dK6}UG38}*pQ`Q4YgEqF-2kxw^;K>We
zdiv|9Vk;LU_wPzdP6+5%bWQSullPR>D1JGrWpmj~skC?zf^t;!7Iu@M>cLi5^C`U+
z6wA~p@`)v`*DP~iz#qRd<>q{I_j^x{*~fHaJs<jl>1o$xH_Umh;GOS}?3#FrKklkm
zHZBn8PQ@fno1~_FM5#J6p21PR24)uo#(Ox~=#Zx&%AeD1uEzmwXdwsvekhd8vk`t2
zQ&bJ$hT<k!zD1WhRF`*P<6$EdyPuZ8h<#pJ|9Bx?Ze^9ofw$0UZpi(&s&)x0zR^VN
zfk-)=jY+)I<2k%JN%oOdCQk&(Jb$#_mf~8k-A{gQmML~$Zh<X_1LX!{t9rgS)>HaB
z78Dw7WAfY@y=oM<sc-308igAWd!=2^GsmtRb5d<(#+5MjDN2(bN@lNh>|o1+(Drk)
zO>2T6rh|}a1M4@W4%SG_BUWjrx)SClA4qL<Z$~VbexQaKkzyjw&Yli6Vqym)2K#0l
z_04075iQ6lQ3%q`#_Ffi;9(<RB&)Qp9^1M<H{pWVlFb!_Fl52N5!Bt-r;bLPDX(Ny
zsxW<Z-ZAgYI?RsiM;o(#v;>2q`6rSPs`GbVrxx83yS~&|<8s8&3M_Z)oLRFLIo(so
zLm99IwN9$EOwP5iTo}&{l<w&LLPWeaQ$`<WARleTV3~h?j3jPBw8&k>wfH75a(%6L
z8-T7ltx9|`^H)ovhh<~jn??QesVbdTT$(AXGLePRH7lDhHL=8kI}piTyFG-Z+K*sq
zYC}}r7?g6;LT$xoS=z1Dg{jsp{Ax)Cn^JxTp00IfZ#sI-dA_JAX-$H<cQxJbApDzA
z^~l6)Q^<`AargN085@I))7;u8pFNe;J9iHqHtzAMm6K+&=~ADHpuPhURa@j=L<O1t
znUt^>e9JLU7_mHJjMf)Tk}y3UrPfhLj^{>$Vmo|;3YqJwnV~L#69Kqmv!83lW_|*%
z0X-|3RXtYs9mk#pP78euUO_1B*g`7Ac;tR<K~AYdVmw;eUPcAX_ZiBngIH=Q*DGPu
zCj$CXNY#F=*QK8n852Y}=Fnfz959-Lu+hj+vXY7|uY~<E&Q_Q#%0!Ak@G{<}!yL`q
z!<76QNioPqZET3+YD-qAe*Ah*sTg3wuQjclNiIH2k>&zwt0Cc^NQ*sHD$m9cWf6?+
zG*jJaOZLKB9Yh!6O&;YQDEmpRjXW=u^lZuXB4?u%a0d6FY+K|G-P!W`BEOS}2nuXU
zoa-efa<QxzqqD<fM}OUA;tEYAHSxGbuM6sIS#nsD<;%iXB1EeRpe&3S^-i2q{Y#<h
z!u68ow9|Sc5ij`q_9Yz+-Zf1ur@{=UameV)8ef=WNmhDHB?T!mR5rF}`lwgZj`z3$
zTOBwUtMCwgf#AOabN;9AVQ_bX<Ep{SDkE-IqShpvP1I6za0=KbuLq<n4{btF%dsq`
zkCSp(L)R~r58|%BJ>v|ExD>Ihm5WaRQp{rgb~$YK#WAxLQ*%~zpD`LQSIYK$lf+%Y
zwf{G+CxViVV7KqE>nLG$QY=unK%ueEB^mXC>ED%c)(rv4>oa~z+WL|mYPTyd-X`W}
z{Xiwo#Tg5S#eTIDJ<#e{!K*EUa^n1<6(I=g+X4qG<W$Z!EoPPhfU6OD&$M}tWUcAE
zLr;nzSWO{Hdw`kIM%Mf#>mmH$nHnzmTI~d({K_uIn<%(}VlDB_I`t<CuZr@(X6X43
zGOMKP9vzh9C3HR9VQkHXKxGx30HPLpiSvbEak-1(hq<W?ekQWu>#F_*7V`NK<E%*4
z)|BGhJv!opnuFG-y+RZhZ022e*@&M75qZ`nZ2=NvJfiJcQN}dx-|iudfde5yD+3&`
zaA8()Nb4_`2$Et>pJe6T{SLsDQoXNE><N~HGAy+cCvW`G|5YOM3SC~ICL0a-wBiI~
zGNxh{SLzmV(`Y`#j7i1|DblUv7w;5EcCl$rtXead)#z2t(!BH^EZ0j)39q<LlHb~t
zyb$@Z)3W{0F|Y9)f(7;cp0D7j?vaoHm;e+nD9iM+5A9^!89y@lS#6vr-XL57J&Gj`
zPj8UeXeqClY-{IBr*RE@GMzIXP_4*i3bVJX_a*=qRSP{<_bQOpiw4q;&Nv%%`$Wso
zgY9`w#jxrUDCwaS9fORCvX6IB%5jz!R{=>_VlJ;VQ?A+z#V(NOHafh-B<(@y;=L3X
zy+&J9#d9IPbaw*>$14IRD#Ab|Gv3@}vu-ob<fazP-mSB%D=e;tL2uv>7iBi){Gp6!
z%+BhLyE*3fvtgKnE$j>+W_Z$DH9>~#tkI{c`CmFbWm1e#eqvu!y8rI1Uf2XrKZ960
zr1E!I0RzUS`K5g@kQX#uFf;@aYArl^iiKnDnLkZ6;pbpXl!XuG9i#p#u9Froi-vN@
zffuus6A2|+5&~j!mp|Ekoa8XRUX!$V^u>hxanW6<UwC>D`x4YQmF&IUfDprz=W~QY
zvW@isKWCOvuTeU6sKfc{m!DFeejXHhbpNqaBp7VMq5U~^QH3hCZ<l1ZbWIR!Ax0M;
zg2`+2h9UEzzM>}Hd&+t5JP|S@p10J~5fSBq!aP-pdPyjMzn@V&U$`b|d;ry%UPzXS
zU1|wel=FI_nvX1b(niT#ZAcL`gOT7-_tF?trunT)^<QkH3$~gZ(?FG&0H9|?MToxp
z1(WAIRth50>YNf<*d<uMLbn%R8}(m#^-=rIrEQn^lQVnaqid;g^)ypGgpDJ%Kdoy-
zM>8qplHw&b8pP6Rn4YD(kl1xG38#~;hUzVuDf*ulbl~C;gv4wQEXM}(=&DD`3cWTg
zh2qxz;H}z-XjP;YNF?yq#Y%#B6YpV3RSKWZ!@23(QQgB~TOw=uc4z~y&T#MIX{Lfm
zDtqC;Sm#D?O(#bxI0{~a4mzQ8^>yS;ubOcaLG=)|lU|9TJA?2XyFYS=`0b=q%+=AL
zLPdOG(c+nLH>I_@^D&M}HVDlo`LTZEn9{xI64I&WZ4Tdo!U9y=6Tv<SSP_F3`%?Vr
zv<ZrK-mlr_+>oZs!h)58GJoQeIZ(mM;jel-jimpzjN<6iCNY@yI(GXKQ+6*PJ8|uz
zf12J^t6DC!RZ6Y8CH|frbLty%kV2q|-OwcP_}3OSe}%y_l4<CT831kaYqA#DwNJ?`
zD#k2FmU=IxI+UKFo0$rLs#od@Z~{94gfe@ZHK^#m?7P~&VksTpHx1wLnMxy{`ajX6
zX>E2yT7AnQp!rSFoj_&P{F{fR!Y>n7;tgT7m|1V~`-iXNMI(F?5rlo?ZLU8Z8I-3Y
za4|)_w&8~e>Eo-SUVjFgHth?ng(cPcG`vo=brOGtI_miw=6!wIrO%?T4Y7s%TfC`Y
za(T1Dpf@KaaIcU?>P?KnyUDDf0b*+R0sF_RZc+<74OdFg%X&U%88Yj_#NOKH$(f7^
z_eq33kt-D)H%P5|ByTU~Z;P&$aKo%zzaAGS+?ANS#CmHWj}!7#n0uqSsHUbsoC?7h
zB-MXM6mxCGgq`T4_iLz6G@oYnNp^ZST2sF3Nz2T^9hW7*vJP0Kg_L8^-l;NE+OqrS
z((^Ipy36-g0dHgZvv0oJL8lG+PCn-~!t*>;B@Hb;dmWIj29Upa##Nw~_GV$5A{wgj
z%CC$)&G|tv<<hP&{m>>Zmz-kfU`wbo<zos)VwD`cNP9|nY0c-mCa65_l%bAFdUDjP
zzFrgqa*XP0oG`qCBrFI1_dZCP4GL`4#s#n}KM@ZJ6n<l!$7EPNY|vuJMiZfT(<d<f
z`_ks`Ls|iu=y$bfDD<0NM*Dm~Pj_GTm?HT9a{*#i3tefym6Dn%yF3vV^E^Oe;ur+#
zNa_GlEOK}eVhcs;(P#@J^O<_E0ZSoPICkGVjKfho1=$;0|4Qw=4GN@v(WV<RMpT0e
zq8(Xb+9DZZF5xbkgm}{=&i7*XTg4kolaV$I(R0UioYVDb+3epqTFs$8@2{n*e@%of
z9ZK*M7Ht*VzO6GIu`-pYoTqL^YZ<wOx3ue0!VAbYsb|Bb{=t{a8A-Lc-TSB|#+gVH
z&OD#B93pUy-+aK*;<%fQh;30gpiS8KtpH?Tj(}H@<hvteMK3P!m;{!J)H)-RkIgxX
zR|{WCogj>Ardl8&l4*mMZt?3Gxm4E;eu4<D+a&>IZ%TUoVIeONLGyj!UuQsb!^}pV
zS;X0u0Yj^Jzy8L`fh!iJOTu*g4zPZ>$kGglPZDtKTzR*{t=i9fUBs*E>TqtJAoNbb
z8=7C;*D;GU4DH)2+-Rj5a2-f(7N?hc6V>hR+2s28S)<igrqyIauPRgsqqm$1`m#_^
zg2_g`P`&b%AVHEcCvZif(EXdk@I4JmBp*SUWuXb8Y*LhNvCB1P_x7f!l|U5JXMDQQ
z7O)%Y-=QZ}^?Q74@f*=GiVHP!xX8uvPCL!?ym@yS6zo@eywrKJccJAbH9`R^G}#I7
zP;lvzH#qK<HR)Ydc&(YzwYF7?tBM2bQh6+slPRKu@0(@F-^E9UYO8XH*cui{OX>R<
zY(u^bLEpDmmT}khVPg$wJ=dG4v95=IJTViWu|LN5HC{zK-!p1FA(Z{+8sbf#@ag_U
zPqqsSJ&VYKi<06dq>KjMx<>yvOEe`yMRLOXI4&CcYV5JsALcUYcArLb*&L7jNBwHS
zQzJ|PJY^krSfWBI4ybwvy{7<-cf9ENQ^2=~O)B*-cS5H(PL^u7#VIUrienUYrV@N(
z1@9L!v$O_gYlu9GpUxIN#alFmvs#m6)2sNiaU3)xrEVi+IEoadk2&R1b20ZWBYdSi
zpnvL8I?}zm4tKLWbW8ks<G+%{VK<Au?8J(5OUdsHHQiJ=W$#U}o=>hHjVu7Vo(O|V
zCiN$y>vv$Ohr-HJbD)MU&zx<qQm?d8W5J~aC7Oy$hjsvhI#7=Ee=&sJ1qf51Sc~~3
zY%|uY;Ld6~XWsu(kybY81|0?59pvqMs3d;me`Q^v7wc9`R3u|`75qnCD&|jXLbXC8
zR1q(8=z=Z%GE>$_%a5>HANoLh<QQa$>K$~xkbP&VB<I0<3EHVAe<;zus)^I~j?rqi
znl2*`sQRvk@X#L>5$X$5L?y2MzL<JS(%;TRyhlGz(ldNO>AjhCX(89id->f%eMv1-
ze@yYkVA9>|U*5EK-`$TP34rK6rkqe`mT)$Z>NiZ*<;Sr!DVR4<xk@zVAAj|g7gZZ8
zdZj(c;;Q#K<IMb4J26A=!}UQ}BEVWHMKjbdpc69raeAoqct;;NO>q#N%>V1=MA`MM
z<DqRAsyoC4Q~B{(^PTHI>w5pA+SL}&zneU`oOAbQ{@tOqw6&0L+Zu#56qBeg4u!T#
z)c;jKgsqL(<vU}u(A^z~$DXqw;;SV!EOU|kNNvpS@cievq@4kKmce<GE`gpL61N@g
zm1*fL%WLNkq-`0&X3r^%gWPSe(~;qTO&{uSdpce~hc$kM17-ZIOPgOHH!RrAQt5t;
zB)#XTSC!lmH&MQz&scTDD;n>m&ktVyM~zNw6YXI!9FqrV<Rl0YjJL<LYv-@T>~`SU
zA<bZ(2y>X_&W7s)+-M-rTS$@#?<@k0|74Ek8vlhT@w(sZ9l$Met<UH@%p~D?5NbkO
zO!8McDt!M|&v@9KXIb80I7%X{^jL_H-yTfyKdH}O*ntR*wO-1ED5~h;iVQ0wse@8u
z$Srlt9+!;-is@S8quT@Ch+=sFpmZ44kHz8*!E>q`3QsU>#|Ds`hWwKHSBy;7ENZ=K
zQ#YF73T~He$qQ?{$p#I;kwrW|HAxYgvU47o%S{daE%9ksQ@xF{1ndjNh+;;<wO{%(
zVqnMFZWDajSfD+4%I@lHo|0I-NBN@{OTm;OO%l0{u7G|}C_pQND)W#CC9SyMI`KmV
zzya?;?}B!{`<Qe5>6TU1mqJL9v%svm?;v`^FUgU`hx8AhwuoDkVj{OO@{Dl{W>l2d
zZ)mQ!qIQ4zHv;wM?)sy6xdeBi<A>mOzV|wJD!O5`GFu!?SzbR)x2X5ifjZDG`ldnV
zzM;H%s?S5d+}IPC6F=r9J@dJiujHxjVhR|NBr24{oy)^SiB3Nk+u=w48ZEaXkJFz?
z^58k@WS+x>BwW=Ci5k$<E-QWG2_%Cp5(yV|=Sd!}B51rY=!Uh(-1&wdE_IZNk`N)f
zklO!GZO;2=2z#MKwkEVbTX>ycq?R!CQ7tANWi;Z<6jsm3OjfHjkbhk~>!k2>4l{a2
zZ7q2E=S=N>H8*{qmqC?g0eZ#Kui5P?a_Dat;kBOt4up${M-IMPhj&2tE8K9B5BD3C
zuZR1-o|+%CS__Ky;^1h(ixsI29u#A^juJ0+GGq>)nJZ!gkKYm@Z_j$}%b}ZiZm_Q+
zWX->VhK?HZ<w|R7^OrXRd{z}UDA<QF15Spn%P0pHVoVx`zOL$k=0_2v#$2N2ChO?a
zI@8=EsvKOo1&-8^Y#nyIYuq?^R0k14h)xWJ4c^Zyqo@Z5$`Y>al{eUvcWW?Mfh$*t
zWW(r;rAUAq!`21YPs1x(1S2u}*xS)v7eM0cQ#L>2Y~TUw*B{G|X8Ah35i!@>!e+Go
zu?B?@RlH9$Yr2|*`;&tO1nc+9)t~6o;*XcBo>xcfGNuYx0l%|HKVB$7>C1-TFQ~2L
zRxn8|3#w?1kU%S(@TuIA6y^Iz5hD-NFM!9WqC|$fI|X$k{<s*hf@UIJPafMY%D@@Y
zc@rw!9xpk0N_}~o7_z&<N6NJ6@XqyKS^bYOB+f#iPRrDFFx8WYF>;YM4i_}!1`lAF
ze)F;xa(okG_5$b6{LuUbifRl=WLb?i?W|^zPEzGk<qjlrA9{NwiC??zUCshAd1oUx
z>W9!+?&49|yG=4sL3sa~!oH81Lt<;y3^gQleAi2HD4@*{e^4D<Z4dn7#^{ychh;`+
z_UE=(s6YdqoC>*+Z^gh-E`(|UFA<6=3TFTrvHJ<9&3iq5?g2u~b+W;!+<#;dL}sJ3
zSqzB{5wb;6my5gV)-{O_0!#?rb8|krhyX`sL;IuAZLImPQ29ckITN1GX~&f{h&)RT
zcGs0d&5y*4^}K6vuI-2=4hx(dXlIM3Uv)jJj}qMlpEa24XL<wJZptoyA5H0fUBRV-
z^Jf6q0H%p9cw3?7j?9PLhUV)_k)*SpQgFwRpD*oS!74PgpO1yl+T{kqVOb@lKGhBB
zw=2y+-^IZHJ;wU4Bk&EXF+qi}OyCUtHmc&N`39)y&1!@Ijsq=sj6e%ULxu=L0#DwX
z&q)oCH(>!)5?;3@)&6*Jbj=G{M*kh`a**E?zO?~sDA2?M89|jL=-|4lg~yl4w2Nju
zYAy5}rtYk!N&|;k9W=^}uRj{SBfACgx10c7Su!dw-an9x(K;UIqt`<!5B#j3&(8qD
zl~k&90o#?C9rHV9k<dY>hClJ>$7?THR(f>7jj%U{TL1wtON8|b0e_u6A>2Z@NIqN#
zd{|eL1v;=SZ6MWx%6CfnwwTb^yLTThbVR_`I?u}vwB^B5u9G_D7!pW_y|?sVep75T
z0<j$^8Izt2z$W_SeMare049#wPXb>DYQHyf1>?bw3A)>=%70l9z^!7=e-4Rv7_G>u
zjN~<nw&bXCmAt~Zg`H~C$W$#2=(y638RntV5c>AUuDq&*-(ZsE>M4AByNYJ%p>I|z
z82vgiZ9a5Ris%fGtH`K2Q4z`>i#p+Gm(Lu^{m>dRB=A>b^Oue@KqyV6ih7b>zqwOK
zyhtnjSs3#qW<`$u!9U!M`(7p<0K5nF7Q)OFlYK1-K$F(=ogw>v-?7YgPWN~3I+aUM
z_jnqQ#!R`&Jy4Kh?~cVPK{3itO<V7tcWu(%{|OSB*pd&~g&1I?JEy2-N$_9nj^VX-
z_dM&Eb_FL2%QzNik%O&RFul+{){md-gr*lhX`_5Dl(sht<!Bkq^^b}CBtpC%wsON`
z76i-M=q!NiU0NYN!v^d3UhGLBeA)z4UmI5;Uhk2Ss;vrbEVOfBEa#=U<Aprf(#JM3
zdiJ9{J${(S$_M_(kLZ6iDKqy}bm~IQo5TOAP+*el{-LQpYkCu(R+UDZ8~{|JDM~QG
zg42u3aAff0sVmEkf{G6h!|t)HIbU_(<RBTKmnlq%c_}}E4SK~;^Y4<z0L~h~&Xphs
zT=>HZ2V;IoOx*I}QSyW)nJrS#bWfYn3r(m`1Aq_R`NpXZ$B<i`m<!#f)x?X)I>2G0
zljuZmv}Jr*&7wGf2A;5hv!P)vRn8zP;}mK+5~xuAVfKKR*iYiq%5E@Jnv6ZNz=~ZK
zq7EO!lA@tPbB8>dqNYIUJs8r=P{TY7#{IZ4cKf4rAue}bcu73$Koa@sW2=oFxqtb)
zWBJA*i@WPy0})Up4_*HOhsfbmb214-keG1?1_0Z#FOXMAg2T?Wk=>KjaY4(VJc)+8
zTPiMh=4zm~40sP>hF}UXw}8A|IX=pk!OrbYnQ<rSTPiQKm#aNEJVCIN4e9Eq;jpV_
zZ|yLT;T_zH<BOs0U8YGJ7@&Tp?feLS{QY+Puh9s^<0txJp{<>R1v&@q7~}7M_ob&F
z>g#zUrm0fxg$k;kKRv@k0KSPr3@MJX*e5I}1~(OX4rlY%;1su;ad3!`V4sVZK%?j#
zndQCt(+NP|prDqN&A;VMP~kTR4lzjF=nA_YSw*X#m1v!3mUe5fB@JitrJ3^KeyIwM
z$T$;Ea2|zk;p-pYHT&0=WG*k0!Ly!k3`mTLM7x%E5|KME0cc$S*p3Mh4=Rqc(Vd0z
zqzAOgPk0lKJb>=@QW^s7>cxUxwQ&SMo~mbLaAwS+j=MkQJ~H;_*B{0+$pCQNB$ja!
zhb-!Y=oB}|J#8K)T-w>l90G}B<9HzB8833fu&aa_{)2im_*GEZ#Mi!I-E^TlnePX!
zAk6MLhA1;>au?7PPtG_V&N7IpfNG&n;JZZ6szcVlH|ZR}0>u?u0<X(|?6Cy|dMxk#
zM=zkUjRD_O-EEKB7zkT|>1Ku#W?6hIm|Sxc8kdun*4aSCeIz;hv-A*6-5(8s!shHS
z-Ov+3$^jYZ9Bjsaxb7{ibEkYe`uZPn76Mb2@<j6ypzyz1g{MPw)>FyMFk;-%IS`Mr
zRE$XT@}-mU#~EkZCOqP`3<mt}`Lg{}PP<=G6n?gj<hw|xC29CEviZU<QD>crbOv+o
zDav8gY3!<E#2-}x`-CTFXo&s8ox#nqztBb_iWu=Q_fhxn-N#|UTXY>yoAFs7m39Zh
zouY^f36XdR8o$nUKO%r(+ToPYiUO@KCEmbcdEb#vpL&*}qWyw*(ZCZ-;AfpeT!3-7
z4_~&(JOV7Ll}F_+o11iu*XH2qE&lnc=8)E##03u|;QA(!N=}1`S-X>G!Y?4@Va6jn
zK6nF|19C5&nTJ}>v4Kpd{eXSE9sdXiA5~tqYP`4=TR#O(@L9D_RvvltgX!?8NkGjh
zg>lXSi25x%()8I&MXdD;q`5cwTZ;N#yXQeMTZ7tRq~^Z%slc=Y_MFN`+l|xh1a4+J
z1oxdYZmeu|B;1G5L39LGQIvt|%ITG?U-|<MjCVP5!wVnC=o~N{fV#Ly{5TMreJPCV
zPAuvtzNIsJX%<<)#qbzKUjM?CuxCB=6u~fDeA4FY0rx-M0N<Mf*JaD>2H>+SsT(-)
z*XQG96HrY8>9jcD{X20jnYRas+`yKRqNw6LOGi^D0ek%E_4>3=iev-Ck@!V%^4Pjd
zgnMxN41cij;o2;%xS?cZJOxq;Cn#2<kL5FM@&oY;nhz<!DlKmsaUN3dno<#_tfqnz
zrPpw-$Be>BBAxbZ-NDUx6VY;i5#6_AN}>>Jnhm~V*4<T&+kVz13#o;sJ^I{BSJcn`
z2ZGJ1%C4r^a)uzw>^W~)D}fw)H)D0$ykJ&T_jV>X^@u$TclH>g(EU&b*}tpovi^X4
z+i~=oZ~t)0g@457`0;g);2(&ZMW685Z+z9Mg_3G++jr?Ff#`I}eI3>7%zm5q8jyk-
zx*Ee;<<`}Z3?jCeB9Mn=?Z^HA_K2tkO;?Y_8Xf^aYnSKVuSa}82jmvglH1-G!cAyD
z@CFu$KEM8I81v4-6cX2=RBGrn1bsJNCi48KSg9!m24^BQJMI?zzO3#gSil>suZHB1
z^gqlkx_W*Pmw6?nM>*u?aIypYZh*1}XnCpqOdlh%HmiKb_3++WdGk+LGB{km6^M*n
zOqt7Pd~eBqS>m!{@l&KOEd>0n6rLX*J<rqhLj!Rn20Br)z%}eYiHb!-Z=&9p^@|){
z_s`nKft=>%(oU^iPV0P3U6*1`oo8IYfyqq3Vb4#O(ZZ9LmsfwEwf{?LDct9I=iz)Y
znQ{!1^;2!&+n*+vi54C;Du)9WU<?B2dor5a{qVz?v^<B7zPvGaS({?$LpNe_q_ZD1
zHJ+5G=MdgqYiQ^D80{V>F<&lxO;#3aE6`9q^v;9v<)odz@cP!BgoSjT5R^d1K9)Og
z25`m$yzY?YI%M&MTj*s(*=Y%|QS|A36kXJ;N6}pwoD)Lld!~#!!9z}$H%pi`%PD=9
zj8ob{A7tN6RwE7-usm1n!ws<Ch`K*T7CAEANZj0k34C#amZbtZP&Ca&%DS;8or8P(
zsNG&f4LXnn1WeNQyjTu-Kr_VGYXoQfLKh({qB<K-CL6cACHuD^SDtb&5&#{N%5?n{
z9>(}%7Np4+{+P*}YJ8=eFjA4^b2-8iUfbrq5sqau-1vm!)AEWxuRm({S3`*4g)F@4
z6-FOBC{k9BTB?9TB4b;7hJq+ywkK*flB7ytcP_4CPz$oyr$LaFK^dv{jE$x9J%+6V
z&vR2fgSsfu`>nRkugFT@gi4&ViifsM3ay_F6@j8_E&wZU&M4q-udMAaEZD55{o0C}
zr}copwxgfYX^(HK<cob|yKjU7{`SQF{cT*#%@8EsvntL@HhTp!eouSoIuryXy($#)
zxDuHIkLBCeoz6QidMJ`U{<QWC1G;qm-*p>v_&n*1-%hj}7~%oh%qwNT8zAmhU$+jK
zRxE(GlB2nS0<OC7fH&ZlGT$dWkNFEy-wxKH>+l@5(O+fvr!*_O06O-Qw@F*X%jMtv
zNAg&@jogEV1TVRo7XU4H-2U@bmy|fiB+jAG)U@&1XxIU2>tE}i#k^y`wh1YCky^=F
z&7kWI$lyERLV~Z|{)|!?odNq9n|!#4P5OjU)REF|T0+a`vguS&1#b5&xpLK$bn<^t
zotChd+)z?qX}H4cS1eC9pYKUC^L!cuXl9e?hH&R=OUe82=~#mTj*P%I7b?w#hTmZ0
z8ey9-8-sNZKB|q~7J`h3_(8-Qd=f^jxFGx6N>3nO3y?h2oAZpmU4Keg5p^=UXtPCL
z%iI4^vUcSKFJ6#g)rA10@nIDW_Zi0N+*?UKu-@l1EIVf+LnAR$73FyX0ykPM(`|(?
z5skXp@Mcn#Y*J7_GJ6JZ)(&i{vvN>IdbuN={n3fW{-PRXB;_phX%&m|YGc%=oF`hz
z*N^J#w;E)IRZsShJ~tz*!?b|Ki+YGDnQG$6ElPkC)Zb)oI-aTctoF;>^f@nu?S<vO
zJ7nMfQ9Y=TddO8Wy3*ERwIvZyHqqJ8!jx3v-N)o|<O|wh5oypFVnGxu5jK^c_?J>u
zLxhmRHc1IAd@^{{fOGk>4ep}}S4VgC*i5g(OQo0Ve!T~qJy~llsvfg`B~bL%iEJj6
z*>w;T-Ox2s{x#bAp|bs=Srl*rkIlYJ>19S-_Z7DA?Qk6y=ZHp$$Id0ixiSz84#=~d
zUk-O4ocE#6^n`@G;5GxTPykkx0RKLQ!-;%4EnWr`%8MI1)ADi@B50)E;U9@cGBm56
zAe&*wDQBwq+?=wiL?5EU)fG}nKrXmr>`#V-9_+Q?hO)e3j)m%ImVwHz%S>tS25xFJ
zwjBrhMK-f%-Fd+gqPko5I@+;xL)V^FqFw!5%{I%_5Rk9)^cgWgm-S1R^q3#GUSOf*
zyz;L20Dw^nWS-;NkBcibxsea&i(h4BU#|s2f;&6?6rE-HUX)1ny|vQdB1lmw`Rr6g
zC5cp^VMumjkO@6UhzK>L`a*R#Oy>_t6Q-k)m5~w#CLu{M$Uzjalw=aH_AJMz1+}f4
zFJ<XrN~J93M^I0Jzs7lwzpZMv{`IQpZTEC;CSZNHTj20225k>8CSkciSr`AZ8s(?<
z{OXWKO1%<OiOiqxBqrN!P2v=Z{3C?2=}-`*l@6?m5d155d5Pn-+?YeN3p8bk|4^+<
zuG)-AV;Pa%n8TrH7=CkQ01x?%$zQLX?}m`pI>=xmfBf*1>Pbo(=0m@XAWtqEM6LW`
z>N50$=0qZLd5H|8gz#91vh;^}4C}Q&jVmduu7+dEMhFcJH3C6GLYg+~)!9*lvYDk_
zT<W!5OcPkS+QdBMB91a>-i9B8{!$8V>aBv5$xP(TPzMm@x?a*1D52~{Lmo?3k6kEM
zj1!&pI<FRe%}wCuMS<f@_o_tdwL*&$W23Zaw@eBMyq%VkkvkEj5+V315Q)5S_z0Pr
zcrA}y#_L-v;i|X&80upCF-VBPLuaUF`=GJP(czMQx9nt|WiAC*!YO||uu8ntpqZ-l
z7iXE<+0i&D!HRiyBdsfOLvX-%HPxa!k>`JH-8Qr5-dI~r@WVyhuai;;fu15lmdQ8d
z`HqKB6`z7nfD8wVm4uq_ZKLYwIJ%NHKNibX`nbgXxMhQJ`Wazl6h^EDvwnw(9r#xI
zt%qoSb0c{<54pb<c_<dU82Q~L!a}sZbpRavj}u-;fz!|@yBR0%lKE}1XeMOlV68-U
zMIxT{h1lzO$FT&C;@Z{NY5KqQcFuyQ=u|Q#FqG&iVlb_IsMbV!4sv=3txOP36=*3n
zrt~_Y>%R*@Cbi{J3tr%caNgVFQMN}(xe0%msPR_?=aY$;xbvfQcm~{O7fhvF=M$Bp
z5xlYqYAP#sg1^;<C?xG-Y>HCTZe`flJ^BBvvlsHWJX>jAYH=h80#ZKlY7L{m>#QS7
zNT9D-=5sU{7HZ>}2#_)e+$Mf{ZMZW3#sO_(q5`J^uNHBlapcxvq5&dHl|jWQ4kb-5
zecT)&rIkP^kc;{~uZ~u!z3rh+4Z-1Ng9*`*?_`t7Hk8K!jMZc+fn#zydGINVNufD{
ze8o8Y?s=3f6JBM!!Y2TPmhp$fR9=_S01CJhs;$OgaTW!mLGpr&ppuT@!|$0ID2KYQ
zgO!knEJ8x4{`Ox~q^=$t1NfZe;Y1C9B`iR0TJM4n)|wu==AX*p{pDU=J(L=<8zb(N
zfCi4wOP**4WwQtSoBrR^RkhE~&{@okS;`<R?lK$yHVX=32=kO%GOscz5=4&e*IQ~<
z{mQ1)rwboOnl4(CL8kNfDd4Y1Tf<iqN~upzrvAP}D`!fCwT|L$F~~K&JdxS0B+<=-
zp;pzxJx=L6yA(LvbJAu0dm3PUz*yTdrJCp!>OJ|lJ=z;wKX`Rk&urXlhKN%JxfqF`
z-0@aA^35(525LuL-h0+bGtm;ykOi_x;$ftuMXE4a_$ER=_!l9n^6Iw@U{9#PWsra0
z1<D(^Ya<f9zrUx9Tf9Lha&d6<S+E5jx^p`$g#hA-W`LN9jGWx_ub&C5BRwS55@Ei(
zW_2B1Qe@(0V-+7>o^s&2s?ZF1Um%(dqDX5Yq?!#7S(TM=$+C!SuLoZPrladc8DU`S
zBaVwz$BMP#d`RgbB4hj8IC*h(Q>Dhr(I+R9hVs{FfK}|XO^BX4A0m*XjX_x0Y9D}F
zE5ME;kt9IF`5FpX^luV>=$}z188#PQqO>x@AStRUd{<>Zp2B<<h*rwz(qXdU$y1q}
zCinmqFK}t5mO)7or|-fVbi!?<!%p6Oa+YORCyuf<CoUp2n3q05gkjBcD$|O+O>*FN
z?U=UPeh$oagEHwPfEy@$NmKb<F*-&nOvtwx_5o|0u>ONX0{^!R>>N4$1X!X0w0$QL
zemkN%dU*ZIU+76t{P689%U3{Ea|Mp)Aa01gsIc$7af-Bp_<qbahU1%SOFPDy8j+nL
zG~ku{ti1cJyh|GflfG0k$>bOC=p053O7f_!MosV$pohO*F2?cYH?{13{Nmibnc(B_
zT2f`1h-d>t@{j`$Q;5S2=FIv4HsyE@#qG`t_h$@k1wVD)*L9d3snB4u<V_ici}=Gt
zT3J~I{k{29s~;`=ewD1#8ziWvuHM^BXr9op-ET}0&8q=ZD;6TfBw4Q%3pQ2ZJTaAc
zg%g}DL&@?@ko)6HIzgnd0zSBKB$#j{;=3I$A{}p<c(V**52oEgVtk}*&Cpj;#=-&t
z_`1zhL@=KlCHBvWh!XEHA_APQP-=efklc5-#1SW%fT01-e1;BcEgv-^xR;p-z8`}F
z7)Jr}w1y7wzQ9OJZz;b_mp*Ki;8%dvP=YDJ&QwF$WJ17(XaQ{Z1r^xrJ4D+|hy{2&
zazr|YcUN+^Qo(L=<3Ajsuq<Dp1U_u3@cHGx^CN(Pq5YDT5Y;%<ISs&d1;<Z-zaHF+
zAeF$^eC+7?w^Sv5SOa&!)(%|PPF^Ux;fmzdFxAgp--3{<^7vdU$JzTDLB~;gOoKG(
zBuspsOJ#)#Bgilg>q<?Yyks&|OKxr0d6wQgn>DW~c(#4(wk3}zV)VdNNFE=5=J4kG
zw`Qlcwqecd$mvB8rhXOFm=JC3oR}Z(196t>>ZmEgDEPw_q<i$*p1%8lMlG$-;}#?!
zA{X2bsL>??y3wBs3JP+#rrg6b0G%FadaduMpgO}&Y(Uv(01!Hliiv?;SX?Xvj$;Po
z%QssPMsUr=9r_h1QV2^U^~i2s$1ERZy@s%gvN21;i^N1SR6nH`Re28qfy==GY;toh
zvJSQU@&b<Ivv8zOEVW2kNYnIVI!iY^UuMV^g@8E|qFujzw;ogEy65rzc5e=DGE)`7
z&%!gn=PGq7z|PZe*JX(_rTh6QW^Am2gURO*gX1d)eokT&rOGDLsgj5uj08(0)T;=o
zRD!|hBLw{V5VTz}rU1zX8Z@Vp&T2`TzmOiuyDBC*(n%qo-^A<nL8{MLl7#{&Mg;J;
za-#56pv3svb>MY5(uB|>L=X6^AmOfjWMk7hxOWI=(wi<A0lA(!mdI+>uUJ@EWm141
zJ>6>Jk%2~4B@jAz{27*wRzZ)-h~{<;#!@2%n}B#sq@M<(^zFB{!=io32O3Eqpi3S#
zG9vZes2l(8?oLKYX#|k?83xe<hmWVoG4E$TYzC7cM?)fDGhD9xnht(<7q}Y22-gV&
zW;(p$LD*Pvx(ynvZ7|?&p027Q8uIcs2lu!3-Ibdr;65lI-Yyr0&~$nqI5NV#X;k5|
zt@>~0=s!i^X@G%=()HI7CX=#C!IZ6FczDPuCrcYYSVleQb5XDp)*31S_K_k_l_DE*
zfDH5c+f4#bvu)|5y&@n&H4=f*(5$a*ositA%v;wfL6{O9&dNfPlz$~=3TAqH_Co*>
zFd^&D$wU2(Q#NOAr4{q;vcrFB!{P<H1*Pr4D@Y^b1$MoG2Q+5Dg{hW+s308{=T-GF
z)u+<qBZ9r{ToCv4O_nNmwHc7As|nJc%;&`bPCO@cSqGGox`{u4qvSt=n$3Wu%!0!$
zcM&%t_)lQ&w#o#wcj16@r5|+oNd`o|)3shzn*4Ce0{x2dnWu4j9W8`+#NX``-yce}
zF@i)&+;<3!+4D7fK;AO|<Y&?AkylrtGqj%;T0AM%ip8|2D+=~dmFs?qWHV-jVWRiZ
z9}VTbqMp_Dk4pFU$A}=AL2&S>ZYE`R_DJbxV^|*QH#APL%X3H^9v{cBd`0`NY35Om
z)>pxD6CZnpOJv#St}eBW^5G9Js`n|XtUtaK2wFj?8g8^BiLh;M{m$D}xUb1s8~xw?
zn!#FE{R{NY+X+3|UmD4uKUJ;aQs{LG7(>>n!<q4<$jmU0-Fm9OR;)^W#bWc|HHhJU
z>i?QWQ9})8gUQy#O)uZ2J+Ti23*h2SaqrtSGBqM<(;-IPa((RwO#l3kuo5Q(7_g-O
zJm4+jIp-5C>gu7CYxl0BQyj`rY3*ni^cylYAs?=Jf^VuErH_lpo{F})2sbs<oj#X}
zvh>B)xKWWH*NDB!X_qVO&Qj_Ei{QrJC-m-`E5}_8FdwA+GmtT3^h^~D4W~kM6@nBw
z=VYT|;Izj+Y&bmq1zVZTx)6{r{YzEm=(g%o<Jo<BrT|WR<ao0GzneB(NDB2?5;Pll
z4d>!!!yto=#rAMuSW8(!{VYoeSMR@=;U}oN_$gb)#JC77U;>NDMw?E=iKdQ=I^nn4
zGnQ}rG1uDu(8u5Q-dfz-i^1>N4P1^)5?H+q)!}{y2@-uE7RZT6?KOXVNy39fR6C=W
zsp&giqXE_z46M<ubmrJalqsE@eGU-UEl@$c_Q5mWnd!$hKkcYYlYdD(#!Z4x{}I5>
z)UFUW8|Y`ql&F{*W4h<(`8GlY7$suii%&UXKV$c@@c)d)aB%>MF5+P=Jsin-Z63Hz
z0&CIMzw7-UeB$04J2IytdLsS1n^$wsUw3^miM`HmEWm@FN)2ag|NH#_<G-c;U-;w(
zP91Bq#ej#0Px<mC4v><=F%G=@t)hM0(F|DZKULDFrzaT&g`r;s{M~;r(6nmxB41wv
z$}1{N?@yP%R##uq&9AK`NXBE6(bFR=FE9I?w;#@IY@BOW0^$rPLXV_CvcPIO00n4A
z|1dUeao$V<wBxa=tE*|~=myT$J7M1LjFbVzx$xupDhhUX3_$LD`1R?=^?FaXj%|H?
zT?f#>b8~l3&CQJkl=C38YJS&D0`h5>fUp9&pdit|zXP!FmIq(=M8MO2cH7H&r)h%@
zi{%7n!0oK`+sWWz!6+b~t*W6B@%=l4g{7tGNCKYGg{D*bwmDE}{2SU_h5WGPnoxL%
z6Y22qumxA1Ehvl8V`m7?Z#xo)hMzz0yVhS7?X!+1`$Z<bb~mcP_ou^*loSzuuhUDt
zUaKj5d_W2JY^fm}5XH9H=<?N2Q4ujTG&IZoC&y5!R3K+*Y1stK<`+PZm(ie2#bEQ#
zA1Pm7-zJ+`@^9b1l`0FrWz5aZ9lbql;(!PRd{WZV({0x~JT(C2Jm>B^?A$3UzM$8~
zW?|UDg?wK3qd^qm@^9bhzJ2{#-0prjHx!Fe-Q8`-a0BSm&PKptd@V1JNFwAFdsz1-
znaW`mH@NWNI|jt-ikBO$)qQ<=fl<c;Gio~=hh+w+@BUKp^W*CVl>PqQj)2>#d~eU<
zc_*HEh%I2l@Vd44Ez#?2MY2k%pcGJ&pPR^}A9vtV*VM#9bIn7<iAEu*1T;}pRaMd6
zo)>$uv)x|;g1I%0+5pJj=4xkn$2=P(3A{l~fS4fJ_unYun={*9owjRju1!I2ey+Oq
z_Vl_8*w44~%CRwD0r3%Zbo8T|x*l}U=|*=RItIouAbBex(PTUSd18FL<ow*JVG__z
zlx<yei+_9^r!Fd&CJ9`q5&9=t+zklphQE;4nIj<~{ac-v`!fo1W&NBftCgO>PMykD
zL1ZF8W3x~tHzPv=h}&klQ+eF#T3T64#Jx{b&PRYU{KLwky0W08`@tj{$&K&Bx|^HZ
z5unJt#fA6Tfr|hO8+#7OfKk5ix>w@<zsk<UsmU~n!vQs*Am9N4K_J{AI6w@CK*B;q
zh9Tg{5ttOQKrrG3D2H;%rGSvgIHKZk8F9c71i3ai5Tb;T(ICgNi$TtS+;XEZ1aLd^
z2W-`*D)m(*srvfs_wDXq_gj_6t|1XA*c%zk4`Yvss)PR&3J*MV`_1dq__(->MI!mv
z>Xb%KcCeb8hi0$OM%g_rE6Wy0;G3@5FW-B3s0!hSPNw&>Xt!Z+{Mpfw?#xmVewv&d
z171ex`SbgPjKlW57r%k$Ot9ESdeuT)Pj4?0VWF;wNG@^BX%R<UuRocP;LyV3@y93>
zO8U*4JM+j!)#MvDh+rzPBl)=D=+A#ONn&6|0=s%@YU&?!x)J-WMRmyA{p380H?!-A
zgbqcupl8?-tcQn3#H#>#Ma5OBwz|4#fodb!o|&DM34A>^7{B-57wYwNbzfiP=*oiu
zqbn~cSUr7x&b!s3E=sv7GED}8a@Js>u^3g@Ki4MJm=I|O1_p>lE&EpiN*stbYYU66
zS<xDyl9N;A(?+jlm6t03mO5eR%azUbD+5X2wx;Ii(@&JV(b-;<V`v@Qud1p#U_`2D
zXiy&?9~ae}b>~KdwRccPQ5G>JRLBXQ1-2=$fXdOn>d6fawwDI~Pz5^l`sdotMR<>L
z)`n0s7X}xBGF=@t?bWpPwf{%c`y^VSP6cXWIg87648dt=YHBt;u)*V3-3vcU6!z?q
zO<r&qGP<{$$z-0ME%Y>|QmL(z`Q*|Y1T{{M`LO3OZlF>d5RGptX^o38rPjmaeFDO8
zk47SF0l+-t+stB#pWoTK|1Pwi`pn0axPW~8qlwCa13R+$snQII!R$`j=qYaSNQW&W
z=<4c92DmfY&~O(9gAuXAb*#e%GIMheh7~(q<=s@e<t$U%$Kb@TWeEquY`)G9X1BMu
zqbvH9y+c9_Qd3i#?y%p}d177$f2<I)V{b1-ixB5y=1ryUVfQoZ>hQq!Jb_fwvPER@
z&=24iP5}W0wnVhP{tlpHo|c%yCnrv{S;Z&v4(|(k`->qMB{!6~Mmz4}68nPfZ}zir
z{PY}*THOK~BAgt;G&D4xJ%7HuG3MvxCHsPseOKQ#>umF^XeIXA*)TRLrm?U;B$KIe
z6j_|Gy!)SFgQna5W@-mjRc~`6C5ovm?YA@T()3#G52>5@1i0RDOZ-C^d;9*2Pc0kc
ztn-=ggu>sOnhK;jpzzudYkB#qUDCG0zSz=s`nhvGP=J1~srf}4=-M)6?L*NYXi@8n
zL3R-%2nftLk4Lcjzcn>E#BEHM0pvO>D?1xQAaI6r`@o|gUk2^)_4DhQ`A>QHI;Pf~
zyNP3J6Mk*pTy}?0Ign&$Z_me>b5{3z6xiMcPrJL`(PC*Fr7PNQg?AJj^Eq|O3633h
z!NbvCPbWcjD3O}Kz*$2UYzv^k0B`*KaC2hR*Uvvauc&xBE_~>H=8WE4Uu{MvEOx%|
zT$RcdiDEj+G)iUP<$;E~MxF)V(`z3=snj;bVo@`uH1-ef?ie(hHQZU-NpwG6biS6m
zH5ZOCH_tIsQy^?E@}i^#@iIU^yIXE4VD$CVYHDhxfEjDpcgea62jQ~%dWZ7Q{4=bj
zF@dt}Wtg*H_VM?Z-X)B?74s3MFZJ=_4=E`h4bkWn;^rc$n(XIyh_n0-n|tpbX?l8k
z6>vq~G+N<@81S^Kqol9X<j4^w6pCr4&5&*ePwZP-SC>2AwBm`&e~*t{4yj699bM@d
z^W$!)di;vN-A+FW{z(T;e2`^?3KR#MM3Nl4bIlm7r#UmE2J(fmwK!-IER~Cv)^)Z~
zQbp5l{Qq2$@8K-yWz_V)Se3}&OL?RDX_jqBfXg1;>6A<<cX4u((T<x!w-*uOpOy2~
zQAX?Ka~i>EZJ~Ij4ePlW`-6M<kHa5YPQ*(!9SRRbEoU#U{jdPl;lsqLXhDvS974-B
z0)h3bZ0#;_6)!C0V7yBM#VxnGLh*>?)Kpn=G4XKO$>KKf&?$faOdz-NCaZ2)TCZOx
z%LQNR+M2({C548XsG0dE6XL%Hb`}v3$xvYf=LcSyn3=T-bdnhCp*RIOW$jj|ax-5x
z*~2$bZf<U(KccY{oyCp^%~LF6_DYrSUb~WR`A1}=qod=_UAxdGCh~}vpfn(yq`E7U
z$#kJm)QXa}W2K1?WYXx0fT59gEOJ8E&Q7ztyPNa{2i+z^0A5azDlvFCfpKwy-go+X
zaKct#e@$s=X$gVI=Hq;PLl`uYR8JCny1P>(3tS!W%nbWGGc)og?FB_rLuFX%h(Z1~
z8hyHYY#@yNp$0<A$InmS$cUbK0UZ5<Lus02j0gNhd*HY2t@Vlg@Gg-^9+3ywE2z0Q
z4c#9+e)2@t%F0UgD$@9Y*-yCE`m>C`WHXf7L{`;-Z)6J`leDF$!T{+~K)`^o1)9cF
z-ri|)IteOL&M>9wG;?HR1YLO~xc$&N@h~)E>MOhFr=dY;%RBjZqwqhjn~r32XKyQk
R_&ow%ZZ4k3E1d&V{|j|B--iGI

literal 0
HcmV?d00001

diff --git a/mmdet/models/detectors/cascade_rcnn.py b/mmdet/models/detectors/cascade_rcnn.py
index 7ecbdff..bd878eb 100644
--- a/mmdet/models/detectors/cascade_rcnn.py
+++ b/mmdet/models/detectors/cascade_rcnn.py
@@ -117,6 +117,37 @@ class CascadeRCNN(BaseDetector, RPNTestMixin):
             x = self.neck(x)
         return x
 
+    def forward_dummy(self, img):
+        outs = ()
+        # backbone
+        x = self.extract_feat(img)
+        # rpn
+        if self.with_rpn:
+            rpn_outs = self.rpn_head(x)
+            outs = outs + (rpn_outs, )
+        proposals = torch.randn(1000, 4).cuda()
+        # bbox heads
+        rois = bbox2roi([proposals])
+        if self.with_bbox:
+            for i in range(self.num_stages):
+                bbox_feats = self.bbox_roi_extractor[i](
+                    x[:self.bbox_roi_extractor[i].num_inputs], rois)
+                if self.with_shared_head:
+                    bbox_feats = self.shared_head(bbox_feats)
+                cls_score, bbox_pred = self.bbox_head[i](bbox_feats)
+                outs = outs + (cls_score, bbox_pred)
+        # mask heads
+        if self.with_mask:
+            mask_rois = rois[:100]
+            for i in range(self.num_stages):
+                mask_feats = self.mask_roi_extractor[i](
+                    x[:self.mask_roi_extractor[i].num_inputs], mask_rois)
+                if self.with_shared_head:
+                    mask_feats = self.shared_head(mask_feats)
+                mask_pred = self.mask_head[i](mask_feats)
+                outs = outs + (mask_pred, )
+        return outs
+
     def forward_train(self,
                       img,
                       img_meta,
diff --git a/mmdet/models/detectors/double_head_rcnn.py b/mmdet/models/detectors/double_head_rcnn.py
index 08f998b..7a78335 100644
--- a/mmdet/models/detectors/double_head_rcnn.py
+++ b/mmdet/models/detectors/double_head_rcnn.py
@@ -12,6 +12,30 @@ class DoubleHeadRCNN(TwoStageDetector):
         super().__init__(**kwargs)
         self.reg_roi_scale_factor = reg_roi_scale_factor
 
+    def forward_dummy(self, img):
+        outs = ()
+        # backbone
+        x = self.extract_feat(img)
+        # rpn
+        if self.with_rpn:
+            rpn_outs = self.rpn_head(x)
+            outs = outs + (rpn_outs, )
+        proposals = torch.randn(1000, 4).cuda()
+        # bbox head
+        rois = bbox2roi([proposals])
+        bbox_cls_feats = self.bbox_roi_extractor(
+            x[:self.bbox_roi_extractor.num_inputs], rois)
+        bbox_reg_feats = self.bbox_roi_extractor(
+            x[:self.bbox_roi_extractor.num_inputs],
+            rois,
+            roi_scale_factor=self.reg_roi_scale_factor)
+        if self.with_shared_head:
+            bbox_cls_feats = self.shared_head(bbox_cls_feats)
+            bbox_reg_feats = self.shared_head(bbox_reg_feats)
+        cls_score, bbox_pred = self.bbox_head(bbox_cls_feats, bbox_reg_feats)
+        outs += (cls_score, bbox_pred)
+        return outs
+
     def forward_train(self,
                       img,
                       img_meta,
diff --git a/mmdet/models/detectors/grid_rcnn.py b/mmdet/models/detectors/grid_rcnn.py
index 2c32164..853242c 100644
--- a/mmdet/models/detectors/grid_rcnn.py
+++ b/mmdet/models/detectors/grid_rcnn.py
@@ -80,6 +80,31 @@ class GridRCNN(TwoStageDetector):
             sampling_result.pos_bboxes = new_bboxes
         return sampling_results
 
+    def forward_dummy(self, img):
+        outs = ()
+        # backbone
+        x = self.extract_feat(img)
+        # rpn
+        if self.with_rpn:
+            rpn_outs = self.rpn_head(x)
+            outs = outs + (rpn_outs, )
+        proposals = torch.randn(1000, 4).cuda()
+        # bbox head
+        rois = bbox2roi([proposals])
+        bbox_feats = self.bbox_roi_extractor(
+            x[:self.bbox_roi_extractor.num_inputs], rois)
+        if self.with_shared_head:
+            bbox_feats = self.shared_head(bbox_feats)
+        cls_score, bbox_pred = self.bbox_head(bbox_feats)
+        # grid head
+        grid_rois = rois[:100]
+        grid_feats = self.grid_roi_extractor(
+            x[:self.grid_roi_extractor.num_inputs], grid_rois)
+        if self.with_shared_head:
+            grid_feats = self.shared_head(grid_feats)
+        grid_pred = self.grid_head(grid_feats)
+        return rpn_outs, cls_score, bbox_pred, grid_pred
+
     def forward_train(self,
                       img,
                       img_meta,
diff --git a/mmdet/models/detectors/htc.py b/mmdet/models/detectors/htc.py
index 7135fe1..d0a7024 100644
--- a/mmdet/models/detectors/htc.py
+++ b/mmdet/models/detectors/htc.py
@@ -153,6 +153,46 @@ class HybridTaskCascade(CascadeRCNN):
             mask_pred = mask_head(mask_feats)
         return mask_pred
 
+    def forward_dummy(self, img):
+        outs = ()
+        # backbone
+        x = self.extract_feat(img)
+        # rpn
+        if self.with_rpn:
+            rpn_outs = self.rpn_head(x)
+            outs = outs + (rpn_outs, )
+        proposals = torch.randn(1000, 4).cuda()
+        # semantic head
+        if self.with_semantic:
+            _, semantic_feat = self.semantic_head(x)
+        else:
+            semantic_feat = None
+        # bbox heads
+        rois = bbox2roi([proposals])
+        for i in range(self.num_stages):
+            cls_score, bbox_pred = self._bbox_forward_test(
+                i, x, rois, semantic_feat=semantic_feat)
+            outs = outs + (cls_score, bbox_pred)
+        # mask heads
+        if self.with_mask:
+            mask_rois = rois[:100]
+            mask_roi_extractor = self.mask_roi_extractor[-1]
+            mask_feats = mask_roi_extractor(
+                x[:len(mask_roi_extractor.featmap_strides)], mask_rois)
+            if self.with_semantic and 'mask' in self.semantic_fusion:
+                mask_semantic_feat = self.semantic_roi_extractor(
+                    [semantic_feat], mask_rois)
+                mask_feats += mask_semantic_feat
+            last_feat = None
+            for i in range(self.num_stages):
+                mask_head = self.mask_head[i]
+                if self.mask_info_flow:
+                    mask_pred, last_feat = mask_head(mask_feats, last_feat)
+                else:
+                    mask_pred = mask_head(mask_feats)
+                outs = outs + (mask_pred, )
+        return outs
+
     def forward_train(self,
                       img,
                       img_meta,
diff --git a/mmdet/models/detectors/mask_scoring_rcnn.py b/mmdet/models/detectors/mask_scoring_rcnn.py
index b035f53..9c16ab1 100644
--- a/mmdet/models/detectors/mask_scoring_rcnn.py
+++ b/mmdet/models/detectors/mask_scoring_rcnn.py
@@ -42,6 +42,9 @@ class MaskScoringRCNN(TwoStageDetector):
         self.mask_iou_head = builder.build_head(mask_iou_head)
         self.mask_iou_head.init_weights()
 
+    def forward_dummy(self, img):
+        raise NotImplementedError
+
     # TODO: refactor forward_train in two stage to reduce code redundancy
     def forward_train(self,
                       img,
diff --git a/mmdet/models/detectors/rpn.py b/mmdet/models/detectors/rpn.py
index 2f947fa..c9de290 100644
--- a/mmdet/models/detectors/rpn.py
+++ b/mmdet/models/detectors/rpn.py
@@ -38,6 +38,11 @@ class RPN(BaseDetector, RPNTestMixin):
             x = self.neck(x)
         return x
 
+    def forward_dummy(self, img):
+        x = self.extract_feat(img)
+        rpn_outs = self.rpn_head(x)
+        return rpn_outs
+
     def forward_train(self,
                       img,
                       img_meta,
diff --git a/mmdet/models/detectors/single_stage.py b/mmdet/models/detectors/single_stage.py
index f7e0fa6..9587392 100644
--- a/mmdet/models/detectors/single_stage.py
+++ b/mmdet/models/detectors/single_stage.py
@@ -42,6 +42,11 @@ class SingleStageDetector(BaseDetector):
             x = self.neck(x)
         return x
 
+    def forward_dummy(self, img):
+        x = self.extract_feat(img)
+        outs = self.bbox_head(x)
+        return outs
+
     def forward_train(self,
                       img,
                       img_metas,
diff --git a/mmdet/models/detectors/two_stage.py b/mmdet/models/detectors/two_stage.py
index e1536b5..6ec1541 100644
--- a/mmdet/models/detectors/two_stage.py
+++ b/mmdet/models/detectors/two_stage.py
@@ -87,6 +87,35 @@ class TwoStageDetector(BaseDetector, RPNTestMixin, BBoxTestMixin,
             x = self.neck(x)
         return x
 
+    def forward_dummy(self, img):
+        outs = ()
+        # backbone
+        x = self.extract_feat(img)
+        # rpn
+        if self.with_rpn:
+            rpn_outs = self.rpn_head(x)
+            outs = outs + (rpn_outs, )
+        proposals = torch.randn(1000, 4).cuda()
+        # bbox head
+        rois = bbox2roi([proposals])
+        if self.with_bbox:
+            bbox_feats = self.bbox_roi_extractor(
+                x[:self.bbox_roi_extractor.num_inputs], rois)
+            if self.with_shared_head:
+                bbox_feats = self.shared_head(bbox_feats)
+            cls_score, bbox_pred = self.bbox_head(bbox_feats)
+            outs = outs + (cls_score, bbox_pred)
+        # mask head
+        if self.with_mask:
+            mask_rois = rois[:100]
+            mask_feats = self.mask_roi_extractor(
+                x[:self.mask_roi_extractor.num_inputs], mask_rois)
+            if self.with_shared_head:
+                mask_feats = self.shared_head(mask_feats)
+            mask_pred = self.mask_head(mask_feats)
+            outs = outs + (mask_pred, )
+        return outs
+
     def forward_train(self,
                       img,
                       img_meta,
diff --git a/mmdet/utils/__init__.py b/mmdet/utils/__init__.py
index c0a1244..f65e3b2 100644
--- a/mmdet/utils/__init__.py
+++ b/mmdet/utils/__init__.py
@@ -1,3 +1,4 @@
+from .flops_counter import get_model_complexity_info
 from .registry import Registry, build_from_cfg
 
-__all__ = ['Registry', 'build_from_cfg']
+__all__ = ['Registry', 'build_from_cfg', 'get_model_complexity_info']
diff --git a/mmdet/utils/flops_counter.py b/mmdet/utils/flops_counter.py
new file mode 100644
index 0000000..3005a14
--- /dev/null
+++ b/mmdet/utils/flops_counter.py
@@ -0,0 +1,421 @@
+# Modified from flops-counter.pytorch by Vladislav Sovrasov
+# original repo: https://github.com/sovrasov/flops-counter.pytorch
+
+# MIT License
+
+# Copyright (c) 2018 Vladislav Sovrasov
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import sys
+
+import numpy as np
+import torch
+import torch.nn as nn
+from torch.nn.modules.batchnorm import _BatchNorm
+from torch.nn.modules.conv import _ConvNd, _ConvTransposeMixin
+from torch.nn.modules.pooling import (_AdaptiveAvgPoolNd, _AdaptiveMaxPoolNd,
+                                      _AvgPoolNd, _MaxPoolNd)
+
+CONV_TYPES = (_ConvNd, )
+DECONV_TYPES = (_ConvTransposeMixin, )
+LINEAR_TYPES = (nn.Linear, )
+POOLING_TYPES = (_AvgPoolNd, _MaxPoolNd, _AdaptiveAvgPoolNd,
+                 _AdaptiveMaxPoolNd)
+RELU_TYPES = (nn.ReLU, nn.PReLU, nn.ELU, nn.LeakyReLU, nn.ReLU6)
+BN_TYPES = (_BatchNorm, )
+UPSAMPLE_TYPES = (nn.Upsample, )
+
+SUPPORTED_TYPES = (
+    CONV_TYPES + DECONV_TYPES + LINEAR_TYPES + POOLING_TYPES + RELU_TYPES +
+    BN_TYPES + UPSAMPLE_TYPES)
+
+
+def get_model_complexity_info(model,
+                              input_res,
+                              print_per_layer_stat=True,
+                              as_strings=True,
+                              input_constructor=None,
+                              ost=sys.stdout):
+    assert type(input_res) is tuple
+    assert len(input_res) >= 2
+    flops_model = add_flops_counting_methods(model)
+    flops_model.eval().start_flops_count()
+    if input_constructor:
+        input = input_constructor(input_res)
+        _ = flops_model(**input)
+    else:
+        batch = torch.ones(()).new_empty(
+            (1, *input_res),
+            dtype=next(flops_model.parameters()).dtype,
+            device=next(flops_model.parameters()).device)
+        flops_model(batch)
+
+    if print_per_layer_stat:
+        print_model_with_flops(flops_model, ost=ost)
+    flops_count = flops_model.compute_average_flops_cost()
+    params_count = get_model_parameters_number(flops_model)
+    flops_model.stop_flops_count()
+
+    if as_strings:
+        return flops_to_string(flops_count), params_to_string(params_count)
+
+    return flops_count, params_count
+
+
+def flops_to_string(flops, units='GMac', precision=2):
+    if units is None:
+        if flops // 10**9 > 0:
+            return str(round(flops / 10.**9, precision)) + ' GMac'
+        elif flops // 10**6 > 0:
+            return str(round(flops / 10.**6, precision)) + ' MMac'
+        elif flops // 10**3 > 0:
+            return str(round(flops / 10.**3, precision)) + ' KMac'
+        else:
+            return str(flops) + ' Mac'
+    else:
+        if units == 'GMac':
+            return str(round(flops / 10.**9, precision)) + ' ' + units
+        elif units == 'MMac':
+            return str(round(flops / 10.**6, precision)) + ' ' + units
+        elif units == 'KMac':
+            return str(round(flops / 10.**3, precision)) + ' ' + units
+        else:
+            return str(flops) + ' Mac'
+
+
+def params_to_string(params_num):
+    if params_num // 10**6 > 0:
+        return str(round(params_num / 10**6, 2)) + ' M'
+    elif params_num // 10**3:
+        return str(round(params_num / 10**3, 2)) + ' k'
+    else:
+        return str(params_num)
+
+
+def print_model_with_flops(model, units='GMac', precision=3, ost=sys.stdout):
+    total_flops = model.compute_average_flops_cost()
+
+    def accumulate_flops(self):
+        if is_supported_instance(self):
+            return self.__flops__ / model.__batch_counter__
+        else:
+            sum = 0
+            for m in self.children():
+                sum += m.accumulate_flops()
+            return sum
+
+    def flops_repr(self):
+        accumulated_flops_cost = self.accumulate_flops()
+        return ', '.join([
+            flops_to_string(
+                accumulated_flops_cost, units=units, precision=precision),
+            '{:.3%} MACs'.format(accumulated_flops_cost / total_flops),
+            self.original_extra_repr()
+        ])
+
+    def add_extra_repr(m):
+        m.accumulate_flops = accumulate_flops.__get__(m)
+        flops_extra_repr = flops_repr.__get__(m)
+        if m.extra_repr != flops_extra_repr:
+            m.original_extra_repr = m.extra_repr
+            m.extra_repr = flops_extra_repr
+            assert m.extra_repr != m.original_extra_repr
+
+    def del_extra_repr(m):
+        if hasattr(m, 'original_extra_repr'):
+            m.extra_repr = m.original_extra_repr
+            del m.original_extra_repr
+        if hasattr(m, 'accumulate_flops'):
+            del m.accumulate_flops
+
+    model.apply(add_extra_repr)
+    print(model, file=ost)
+    model.apply(del_extra_repr)
+
+
+def get_model_parameters_number(model):
+    params_num = sum(p.numel() for p in model.parameters() if p.requires_grad)
+    return params_num
+
+
+def add_flops_counting_methods(net_main_module):
+    # adding additional methods to the existing module object,
+    # this is done this way so that each function has access to self object
+    net_main_module.start_flops_count = start_flops_count.__get__(
+        net_main_module)
+    net_main_module.stop_flops_count = stop_flops_count.__get__(
+        net_main_module)
+    net_main_module.reset_flops_count = reset_flops_count.__get__(
+        net_main_module)
+    net_main_module.compute_average_flops_cost = \
+        compute_average_flops_cost.__get__(net_main_module)
+
+    net_main_module.reset_flops_count()
+
+    # Adding variables necessary for masked flops computation
+    net_main_module.apply(add_flops_mask_variable_or_reset)
+
+    return net_main_module
+
+
+def compute_average_flops_cost(self):
+    """
+    A method that will be available after add_flops_counting_methods() is
+    called on a desired net object.
+    Returns current mean flops consumption per image.
+    """
+
+    batches_count = self.__batch_counter__
+    flops_sum = 0
+    for module in self.modules():
+        if is_supported_instance(module):
+            flops_sum += module.__flops__
+
+    return flops_sum / batches_count
+
+
+def start_flops_count(self):
+    """
+    A method that will be available after add_flops_counting_methods() is
+    called on a desired net object.
+    Activates the computation of mean flops consumption per image.
+    Call it before you run the network.
+    """
+    add_batch_counter_hook_function(self)
+    self.apply(add_flops_counter_hook_function)
+
+
+def stop_flops_count(self):
+    """
+    A method that will be available after add_flops_counting_methods() is
+    called on a desired net object.
+    Stops computing the mean flops consumption per image.
+    Call whenever you want to pause the computation.
+    """
+    remove_batch_counter_hook_function(self)
+    self.apply(remove_flops_counter_hook_function)
+
+
+def reset_flops_count(self):
+    """
+    A method that will be available after add_flops_counting_methods() is
+    called on a desired net object.
+    Resets statistics computed so far.
+    """
+    add_batch_counter_variables_or_reset(self)
+    self.apply(add_flops_counter_variable_or_reset)
+
+
+def add_flops_mask(module, mask):
+
+    def add_flops_mask_func(module):
+        if isinstance(module, torch.nn.Conv2d):
+            module.__mask__ = mask
+
+    module.apply(add_flops_mask_func)
+
+
+def remove_flops_mask(module):
+    module.apply(add_flops_mask_variable_or_reset)
+
+
+def is_supported_instance(module):
+    if isinstance(module, SUPPORTED_TYPES):
+        return True
+    else:
+        return False
+
+
+def empty_flops_counter_hook(module, input, output):
+    module.__flops__ += 0
+
+
+def upsample_flops_counter_hook(module, input, output):
+    output_size = output[0]
+    batch_size = output_size.shape[0]
+    output_elements_count = batch_size
+    for val in output_size.shape[1:]:
+        output_elements_count *= val
+    module.__flops__ += int(output_elements_count)
+
+
+def relu_flops_counter_hook(module, input, output):
+    active_elements_count = output.numel()
+    module.__flops__ += int(active_elements_count)
+
+
+def linear_flops_counter_hook(module, input, output):
+    input = input[0]
+    batch_size = input.shape[0]
+    module.__flops__ += int(batch_size * input.shape[1] * output.shape[1])
+
+
+def pool_flops_counter_hook(module, input, output):
+    input = input[0]
+    module.__flops__ += int(np.prod(input.shape))
+
+
+def bn_flops_counter_hook(module, input, output):
+    module.affine
+    input = input[0]
+
+    batch_flops = np.prod(input.shape)
+    if module.affine:
+        batch_flops *= 2
+    module.__flops__ += int(batch_flops)
+
+
+def deconv_flops_counter_hook(conv_module, input, output):
+    # Can have multiple inputs, getting the first one
+    input = input[0]
+
+    batch_size = input.shape[0]
+    input_height, input_width = input.shape[2:]
+
+    kernel_height, kernel_width = conv_module.kernel_size
+    in_channels = conv_module.in_channels
+    out_channels = conv_module.out_channels
+    groups = conv_module.groups
+
+    filters_per_channel = out_channels // groups
+    conv_per_position_flops = (
+        kernel_height * kernel_width * in_channels * filters_per_channel)
+
+    active_elements_count = batch_size * input_height * input_width
+    overall_conv_flops = conv_per_position_flops * active_elements_count
+    bias_flops = 0
+    if conv_module.bias is not None:
+        output_height, output_width = output.shape[2:]
+        bias_flops = out_channels * batch_size * output_height * output_height
+    overall_flops = overall_conv_flops + bias_flops
+
+    conv_module.__flops__ += int(overall_flops)
+
+
+def conv_flops_counter_hook(conv_module, input, output):
+    # Can have multiple inputs, getting the first one
+    input = input[0]
+
+    batch_size = input.shape[0]
+    output_dims = list(output.shape[2:])
+
+    kernel_dims = list(conv_module.kernel_size)
+    in_channels = conv_module.in_channels
+    out_channels = conv_module.out_channels
+    groups = conv_module.groups
+
+    filters_per_channel = out_channels // groups
+    conv_per_position_flops = np.prod(
+        kernel_dims) * in_channels * filters_per_channel
+
+    active_elements_count = batch_size * np.prod(output_dims)
+
+    if conv_module.__mask__ is not None:
+        # (b, 1, h, w)
+        output_height, output_width = output.shape[2:]
+        flops_mask = conv_module.__mask__.expand(batch_size, 1, output_height,
+                                                 output_width)
+        active_elements_count = flops_mask.sum()
+
+    overall_conv_flops = conv_per_position_flops * active_elements_count
+
+    bias_flops = 0
+
+    if conv_module.bias is not None:
+
+        bias_flops = out_channels * active_elements_count
+
+    overall_flops = overall_conv_flops + bias_flops
+
+    conv_module.__flops__ += int(overall_flops)
+
+
+def batch_counter_hook(module, input, output):
+    batch_size = 1
+    if len(input) > 0:
+        # Can have multiple inputs, getting the first one
+        input = input[0]
+        batch_size = len(input)
+    else:
+        print('Warning! No positional inputs found for a module, '
+              'assuming batch size is 1.')
+    module.__batch_counter__ += batch_size
+
+
+def add_batch_counter_variables_or_reset(module):
+
+    module.__batch_counter__ = 0
+
+
+def add_batch_counter_hook_function(module):
+    if hasattr(module, '__batch_counter_handle__'):
+        return
+
+    handle = module.register_forward_hook(batch_counter_hook)
+    module.__batch_counter_handle__ = handle
+
+
+def remove_batch_counter_hook_function(module):
+    if hasattr(module, '__batch_counter_handle__'):
+        module.__batch_counter_handle__.remove()
+        del module.__batch_counter_handle__
+
+
+def add_flops_counter_variable_or_reset(module):
+    if is_supported_instance(module):
+        module.__flops__ = 0
+
+
+def add_flops_counter_hook_function(module):
+    if is_supported_instance(module):
+        if hasattr(module, '__flops_handle__'):
+            return
+
+        if isinstance(module, CONV_TYPES):
+            handle = module.register_forward_hook(conv_flops_counter_hook)
+        elif isinstance(module, RELU_TYPES):
+            handle = module.register_forward_hook(relu_flops_counter_hook)
+        elif isinstance(module, LINEAR_TYPES):
+            handle = module.register_forward_hook(linear_flops_counter_hook)
+        elif isinstance(module, POOLING_TYPES):
+            handle = module.register_forward_hook(pool_flops_counter_hook)
+        elif isinstance(module, BN_TYPES):
+            handle = module.register_forward_hook(bn_flops_counter_hook)
+        elif isinstance(module, UPSAMPLE_TYPES):
+            handle = module.register_forward_hook(upsample_flops_counter_hook)
+        elif isinstance(module, DECONV_TYPES):
+            handle = module.register_forward_hook(deconv_flops_counter_hook)
+        else:
+            handle = module.register_forward_hook(empty_flops_counter_hook)
+        module.__flops_handle__ = handle
+
+
+def remove_flops_counter_hook_function(module):
+    if is_supported_instance(module):
+        if hasattr(module, '__flops_handle__'):
+            module.__flops_handle__.remove()
+            del module.__flops_handle__
+
+
+# --- Masked flops counting
+# Also being run in the initialization
+def add_flops_mask_variable_or_reset(module):
+    if is_supported_instance(module):
+        module.__mask__ = None
diff --git a/tools/get_flops.py b/tools/get_flops.py
new file mode 100644
index 0000000..e64bac6
--- /dev/null
+++ b/tools/get_flops.py
@@ -0,0 +1,52 @@
+import argparse
+
+from mmcv import Config
+
+from mmdet.models import build_detector
+from mmdet.utils import get_model_complexity_info
+
+
+def parse_args():
+    parser = argparse.ArgumentParser(description='Train a detector')
+    parser.add_argument('config', help='train config file path')
+    parser.add_argument(
+        '--shape',
+        type=int,
+        nargs='+',
+        default=[1280, 800],
+        help='input image size')
+    args = parser.parse_args()
+    return args
+
+
+def main():
+
+    args = parse_args()
+
+    if len(args.shape) == 1:
+        input_shape = (3, args.shape[0], args.shape[0])
+    elif len(args.shape) == 2:
+        input_shape = (3, ) + tuple(args.shape)
+    else:
+        raise ValueError('invalid input shape')
+
+    cfg = Config.fromfile(args.config)
+    model = build_detector(
+        cfg.model, train_cfg=cfg.train_cfg, test_cfg=cfg.test_cfg).cuda()
+    model.eval()
+
+    if hasattr(model, 'forward_dummy'):
+        model.forward = model.forward_dummy
+    else:
+        raise NotImplementedError(
+            'FLOPs counter is currently not currently supported with {}'.
+            format(model.__class__.__name__))
+
+    flops, params = get_model_complexity_info(model, input_shape)
+    split_line = '=' * 30
+    print('{0}\nInput shape: {1}\nFlops: {2}\nParams: {3}\n{0}'.format(
+        split_line, input_shape, flops, params))
+
+
+if __name__ == '__main__':
+    main()
-- 
GitLab