iUTKc@{I6
zgH=Ks#fBUk6w$~QH~J!Y5E7=}i^;S2zD`a)g1_nA;VWg
zx(rTHqHP#|G8CaoEt^Qkkm`)Xni+4OT^twq6*rsZb$<_zM;6Qmo^r`F>K-Jn23WfC
zmq+9bzZFhOoQj&2GjoE!kXN21x2hnK6A_@q635
zhn75p&h&7yyDacRv`^ntrVvZ|)!og2%#|39Q)_GJ%-z}OzIppSRb%mC*?s<<$&`(@V@WDca?
zyk9wxQKqG(WoNWNe0}z7USdOWyv(&@(T3+#PwL^IOlI3KNo!mykyz=uoR)xAEu%EH
ze9?9aMSgQ6s^J0h+O)CQIpDU}$2I8p7fYQZF~GPF|fzY#p1?rp1~G<
z#*Kfed9~F`b3>Odo$cjQe=Fy!-eFO0=M2WK2-1FkEbH*cCPMxNJCEPlB<=?yro
z`IRCSKyYQb6zH2R)3P^~^QN_*$wRv8w1hv5dD=F+;_`P)%&Pum
z>KpKOa5M1XSCr=Rr|#BQ7jFdK()QmZHb)KDpueG-6ZU;2m79Cr0~gADoK;+I%ey~I
zjpmRPI(k>;#jsN?MJb(gdX2()HaxJ{yP39&+W6@!p5_tKadC)~UnR{hF@u--_+fNLACUTpnp&p7dwGb3HqgtD~Dm
z+q}_tDqDKHy1X>L_jkj8K0BecG7?K%TDLDrk4_R8{qZ%o79F@cfzQ-%?0+_2eJA00
zG5jWhdad(nRL0ZPU~{5%_kfs&=f(BOat4ZG!0p!b$tL|%CTjk0?v;l>zkhA^@nw}R
zwuRo5dHH!Phi1Q1$nNmUJQ7EMH`Te5h9mBpgu|Hh<(9oj)r-;~a;kO&?@n&>Bw%(nV
zdasTjzTDkXw2qMs2j}kvY6aNB9hNPP*-Vg|zxPmEx(AvqgI;}p6-(&e8F+a$#Lz;v
zfBUDH@iYHSFZJV)-D|0-@XWMbv}yLWsuLz+h`H
zK2sQ&D?{I3Y-n4>5XFU<4J{_Jp*%Sl)lP17Uek@C@n9MiA$c1b3S(YG
zMDji(_JGItgD!W37HA|WEgmzG2Xs07nJ?cz&3&4sCa0>&(4+}=&91d)#5ocWaLc~=
zasC@fE7heXv-C{6%5i2BsE^7!$ZL-6DsY(b30LV2t86H8`KRiO%r|xhJ7@26!)_>c
z6f`Da$I*5E$_za;PX?$>@zLEyXcd@~r3}x3n!!4DZ{YqPE@VX(`Z)%s3>$zL$Unvn
zLZKGvyxNtdJv1qe=x_|a@o)01LZmFd&KCOd&*e16S}J%;?eIObwo#qpCyQo+X}Lz2)>PtCso7xi
zhHTJYR#A$qXcSUppXybIsT^H+%9gd(ecS5oWkPXxFq?#S$xmygur}te%Xp*+!P*`y
z#RG#HpfWEUx7TW)U#Ao_JBYp>5jU!DMV7<(3guObLfM76&1if)dU)NB*km`O!PI?N
zYW{SeFwmpQL8LeTjV=pV6Q4XGR*{?(t&fpy%b2uAVJ?{^`82R3K?({CkMuPBJe9jl-o5Dzna#=OZawU1pr-8CT
zzwp^*%T=QOzAPG~N
zl;vFq%YxqOBHxo%l2U)liwnu3hWEDO()2m(kJ{CR9%%HCNI9Xjg_c~2&(_&}?HQC!
zKjOQcP?0v9YAil4;WnWi(q>M^gJPz^@25kszB{xYDz_8|DI>K
z7_cO(Cr&v2umssMe0x>(@ruiDis522QiPN&MU!pUvglQBbgBEallp{uOMs`a22>)>
zFiuXGRcgPRu`a=2uLKei)4?C3_@Syl>*-o|S?-m)kP;bIskWloH#{XuU(O$LneuMM
zS$bR1VmXYYZrG?pF%$qHd+x9E;ai5O3}q!2<9ovo{?jabk!l#t|w&rA+Af0$|dudlkyZ}Rcd
z>iCA9Kb5L(OXB`YPm-moO<|UWWV7Z}D<&8c4$+dl>?=k(HiVMl*3Bkk`Ik=n7pCj$
z_G^qK)17T`51suo#Kp`?pN)`v2f){JV~JnUdtN++HL#RbLKpJ5WF%ZL?FhRh3SV;i
z$c+g3RleNn!%0pAf>B;#IXY{XUYEITA`4wCwtwVBm1?W|`UmMzGeodqkq2(JFxn@q1VSDt7;U=2WauKu=i59}JJ-)!?D{ZPmpK-KxDUu`>+`1FaXd6RyH
zC$c?*(Pq)YbFvAQ`h|6{luKz;<#%vBID3^MxT(KsD2Rs?>zC)R*vJpbiBaSe$ex}J
zx*$}GOYn0FGclClB5yHZhMaow?kp111~~F4Sl@?)8kwi7yrIk^pvFN35?CM1-hTHh
zR+u99d$mZ31{ztDunO~$RGRm?q{;IY6Wu9lQ8dgWYP1y-DO*Fp)}$R@V(6L&ZbmAs
zdA3XujU!E-+ukAIHcKGOK##cO^OQuUSek0~C*%zy@C;K&Zd{NQy
z#lhia12`fR;ptBkh(vof`~1h{S)spQG~l?iq1o@+cY6v2%fSt&Bnmx;Xlm9uke}F6
z80}VK3R9HSY_fpE!AH;ZOQ{gH#={>d!~?|g&or`Za>*vI2|&kYYoJ0OjjVO@@xX2%
z23J2$5laz_2Sc_ft1;`H?i2xTbOTC?xw7|wWwMnjP1bAcx)0A0&%)9McZzLaGh;kf
zvv!4$*x6TeT61<$hXb_}NJ1jr-jsx=5R~G~>a(%Vt63_diePc-;p`$B>Zl_oKXZkU
zyuwmVEq^Yk^P00$$eJd
zq7HmOIulFt6Aajujpg=IJQX|~HJOu&Umu%&Zk9GQRWmgwJ=i&
zJ##C!DGq_hJg8Fw*U$eXa#7JBZy45!!$wNPNfMJysbH(0iD`^_&hevGLVBgvGX!%csiD6E9LYAHvEz$lGzxNJ_KG}
zpwoH>RCK{Cza)s8=pHcbXcziKQURcw8Mh`c&JUk~SHY!-ULvk8&nph{H^+
zuECmNqIc<9e5g>jFLNWbQi1m)-m13pd)YW0qXR5(DHFrR=ros*^MvQ&R`*5BQ&;w=
zr4R+xb32dMyx{Ro_b>y-X9pG%b?Xl9{af?o;a?;>>SOmfDwN}fLx3!cA3{I2)y9ov
zQnQow;&3qC4AxRROaWMcNK<&hOTHD&aES}on)EW!c5X#=5{yr+O$!i{H&_bVxmf*^
z)g68fXcQ!j8UQT%>L}85PS|3n_b0!=n*@oBOR2N2D-Q`O#+$CWzPjT`
zfJdIYjRzZ_+V%a6S*!%uJQ?w$(8re;M1P51rKa6=Vl5e^`efKW3Hj7S1je5pD>UJ|
zezv}XJ$}1mmcdm*>@mZd#o3rbV}Ksxe%pT@k9@I_MTY|gY>*|I4SKOzg}!fzEj9;(5EvcB*w}MIP$Pc#wv&7
zJ*(Vm%5Oav(^(QZtR2XatB^Y#`G)ZgVz>FhEOW4iX11Def4QhHi@{dsBCOY1+KcZ_d))5u)iw5mHNh-{RClaFhEAhgpJgNz~q08PGgKrwvgI!+KUN
zaxTpCj2kTtNBZi3YC488xHWhsole4JDJM$QHI}10e$vV28BvVAG(?C{IlW*34U3)6
z;II}0EqNdk6$d))fUfD`{Mp2YX3woVBQd}Vd9yUHH{9T}@u#{nV|>-)VoQry*j=sd
z;)1_DOCt$qQp)!Ig?W^iHD?v>YvnGAd{0YMaY6eje3`0K
za(I-CjFV6i9D!$(=+CUIEu(XOk^KA}H|AnuCC6=*ciY5O+u785Io4Qq-1*?U5Oj`|
z+0_g6x?E_AbzM?T8cA4xmX#UK+F(XxzyIEA4h5s|VgUf!%;n)Uj{Aj{C3Sruh0m`~
z=4k%~ZBs?yg@aUUZ!(HGNE7iST4MsmcIva$
z?T&!7|EC46{hVzdmlu{1+iNQ;X0LtHw9-9{a1<@os{IfB25=CD_;XQ!G3Q?Sx-5N^%=|6m&QExdI=u%
z$igQTNN9Ibs&1V%nX2-!!52H#JgFfJ3Ksj{E#YR??lxMW9`w-&mlv71-(l9#vb<-j
zx2_$KIG@hzWh!ql_{Du=M(yD$1bM$C=yq8BHa4fa($(_NWQqL@=xeLzkJF%-7q;?*
zF*3VRKYyrx?Ne10vyRor!`|^%_q(yRFFN*X+QxGi#V^jg&%>%giy4-k>l1V`3l`_k
z?13S(mo?wWeWEmTi|h#rn8n|!M6_uHQwuv19OsUNj(F+_zn^wcp3;>gw8})&p0d(3
z;hsQS7ana#3c{+;y|ghT@_di*gIRRBg_AiWGYG}-47*5xDs1?fXw1hkdv;&tle5q!
z-xs{WBIU7-M4?qAfBTiT&!rn!aJvYfE{(q+|6f^X&&B0z3`{*}|?;osM*L)om*SpQV?Nlh-R$wU!b~2mW{G
zMyn4aQI@Z+4CvujEA8HHcR9mtNWs`JCyVditII2517{!l=_8&u#P*R|pHNi}?XH@A$ms){zxSv2i9w(*hq
z(-pPwwl;7hvGbJ}xRE9vKaN-?Xw~xJw(KL76H+C^g`V$Yl%q_0LvxU0eT#YX)ztp6
zd8emU!*f~57wv5=Uangj=s(d2$jCbCq)Vn7e3)?km4N_eiiTKElk((LRqbaBrnIte
zYA2~uyGpC%^ZdE3NK)0*Es+$`kJT5b8KZ-&6Q9Fo)LC{>c6dNzRv~h!=W=^h1*zIX
zn~WMG2nC#qPJpFAcW9jh^u&r2
zf5y@GjU7rh!NZuVq}SSet-}|7=x;aX9(Etg7jC0o+}~^MeTo3fNo!8`&DB&R$u)H4
zlxj?Ce|cG|m)>YG9v(9AMWuHuRUP~PnIyh{o5y&(AQpJ~Sf6#{(=UHcg<%BOk5`)X=tF6d8x|7;gPrnUZcY31?fSn8h9P(W?!36XkmiU?R8q`-xZD&zx$
zQQGXg5C5|6wlW282|*-4Hq3Ht)_C@HQ(5*!$z
zGk)CG`+Dh1k!|=R}Y7oCneJv7WvSGz7q@G$EC7qQYPXR=arT
zq|to8)ci02JoQKA0@bwY{?T5~w{A(p@tqEO-8yNm0tM;&kqoO)A)N-Q^syceNj!M_
z7KvIT*#WCsdhi#kY!GV(5YRMeo+WewDTxfjYu1vr(YbwY7Db!xgyQxRI8GHORLb2Ix(L9&*uL7;x-+2!*oi`_)SIw5!Tj~ye0}50x
z9Bd
zkddGdWmn3xV2(-N@?Z%eTsuvH+4jd6h*q9TFrPEao23!a`)ccegShio-tFS*waUXC
zdg8l|-$UnI_bWkH%MY7DKP)Z-e!JcwsVqA^cl$DAZg(wj$MQbsPrV3Sn>u-^6|R7W
zqpg9Vn{^f2>2@)@$y?IAdg~+OF1g!giS#I1#<=;=;^%wn>xFzMy}Hm#TyFhPFF`EW
z6mlLW^I%MK@b=-i02A^S-L=!-PucTYg32eEfrnPF(O3T9-Qu@I@POw1LxRgF3;-X^
z9R8~JlfZdoK&866l?rR_r_h^bRHw%HR)Z9mAvJ*42Ga6G`^Im}Zu)fS1F
zm~d5)7yzY_F7OGiAv>xtr?`Z5!E!>eKbsQ!;d)}cH&x5?(3zZ`Vr3Z(tLoLEwO1Qo
zdB2koVqN+5J!6+FEN#NJ
z)(YCDsnZMd8RW`?mDz5LmgN0H-ou}h@mHP?@je}=qZw*+2QaaYj%*tfH3;v7f3{8G
zx!GKT&rz#(=?>gwh>)+%N2iHy>MdKMzlE=V?G#=tsYkFb$iK^~Ui`xwx_Rv+97ld5
zcY#HojV03yA^`^siHzvSDzEKM*e+RwNUsHDZ(+hSLRp*0kucRpR*WhuYl`4Tk++hQ
zwC*j`kQE*}=wTY4LjUcl-}Mr^G>9vE#>#Mm4Uy=j%-EpT1|B*|m#H`&=x&d~4~u`y*E%
zX!r8|vUUb4>2vh0nzuRk`lp`2-L7N9)gSJt%_-sD$eNhC^(KyZ6S91UWy$g~mFJ|4
zbva7*WQAY+eSTMw>cLhekZ6z1=GX#3Ev!&ii(9vq!C#AtXd1FkA0>_Vz7bNl~{5
zN*cS~pY(i#Ww|#gKc<(I>9Un9o13!ddD10$E4gI?GCp|w7uR&nN9_+I?Uwfvy}O{Ow&}IWYoGhJ
zAjt8&mkda2Q-MkP)i$XpLD
zKg_z`E}g#}SZbMYwFo%r%lol)2(>!?bn|o8O!Pv)ViiJthn=BL^I9n@fw=Xx%}?O`
zCqA)?NV%A96MN1lyA!P?9G~R6Vigr+&RlYGz~KfHOG1U}@vJ|$6njQ&F+rNRI6yW%
z?zk+qQW1OwxiQCQQu7n2=!z^?Kc_8)-K3=_VobND(ha9n1d3}D5(3z@TTExF$rZmY
zlKjUTK2%PAsHu%z7uszT6*5v%D5P^1D2oW0Yx=L_(2Al^b`9
zTpvGbE#3Ut;*vg~^8rM*-IqQ5a<%-P2~2mjkbambx;#GMQ-9ACu=VvWY|$|4_;$_5
z@Rjs;sljmn!1WW~{hVQ$%WjE>g|ggs5lNWQ3rUEJ%hRmm2{uU#QG=%`SYq>W4jQrY>OZXxKnB2i
zK!c|;Jd4I4vn@jZo#B^AH^0K}R>cdn4qNfuX>oiC{Y&uJa0~ru{kHV@ua%3Kcqp$3
zz@I19T9|>C9^aUDD&VP3a!I`M6Cr~4Jfrek2o7jE&M>7%Jb#7N$*$J&O}}NpscXPd
zL2c0a&@M=2c1xrMm9KdvX!rN<<#NzmUcl*L@0~e+Kk3zQ4NC4K9GI=WGS!Inzb;wPb&Xmb#B}x>cbrd4N34P3%Gua6wOwr-)Yd-$;
zXVc9=s0xsYz))#$(Fp-2WQw3AR8?e@&rnCY7|L)PQgTD|B6I_V`OZcD!pG0PtL-m;
z?EK4D-@N|SA1^lg;Y!=pmggTWd#_%+ef-YN)d2?Iy5}!mxbfiQ_ivsZ2QJa#bHQ
zn(I9jQ7>t@mDfore8A#PQ0X
zeCEE7ee$3D!~f$LN?IjTCbt@tlW58UFGTmG+`C0#XO_Uo?6PoKSXy}0@GM4!Xi$n#
zdXZ&HGC`&^X3IiYRmyv)T6QNoV5W>>K1G&P7hQdd?#wRL#2T_N@zJM0Z$^j!WMQae
z6km{lC31jel!h!t08@laYy}0;++AWa1(7mjPZJ1B$@2ZL*`)XHWFI?XA9L#@o<+Wks
z!s63^b0gxn{>dLB-Ex45sI^R83z-yvo28uIEo){&H!Q?7qeY-FR082-W_T&q(K#0s
zslYOWQ5(7maurvTLrC<&OhRTt)`?2>BmuYV9p={B%${M&OaU-MV&)Ui{EBRZN-_*d
znyHo%U1bntNvOe#5*|H=G1yB$A`~bj1QFe%s`k$Ay(GJ*AiVF)8(X&c<-hjO=3S>=
z{^5SP{>tfJ`So_~`fT;B|NEa`IhyZya0eG2e(3~}GXMY}07*naRQ08cSKn<9{_KY^
zv3QAS(b1x9pV)fn!;f71(NEv^=!3hL-@f*ZKX~Kn;Ugb9TUOIu58wOX`*tq9wSV>X
zx6beE;Q3E|^&kKCxrZKl?T0TP;J$7?(~CD|fdi4nos2H0YB4mMt=B9pX~AA-J;P|>
z7+RT1cOh_y!zu)PkfGd;O5tUczpZy#RI2be`6PCFMR6{uWX#z-a2{a
z!o%m$T)j<^M>h|i`o*WVmpg~MyXWpZdFjV*z5F}ByI#~YInnSl&)@m|?_Iw0o>RNm
z5AXfVXJ7cOubqGD{_p?!w^zIubz!|VGbZH4Ai=GM(v7WSAn6F96f$LEBv9l6Xb~}z
zYrrT8v|^E7jMUV-(-Z|3&E?@@Q}?liXj7=lM5F-9Ng*ex03k2x>e0Ph=G;9@7D7?-
z@n=4tFbU}~7*?`}WzB
zT8w$HXI>up#G^YqCw}iA{oc*v1G&ze!t&ZX<-tczuXoEvJNe|(2d}(YH&5>E$LlZs
zbS~#Gcou1L8Kt0&g&A&wXhZ1{1kIT;&lJtl8hcg9TqY3g1}8h{nJ7?NRW>t92@&RM
zpru5Iy4<~W7y$?f0%pDjhF~CrC{h~xJWI;(#587EjlDp@N1y#XUDlBpj7b{AVJKmZ
zM#`BUJ{3|iBr}YJ96dP@O-YyV9vYMY5lBx$FEQ4E?mC0``Q~f_HMRF6J!o%AyuM>^(qSu=?xi$B?WfT*hnLwge)yBDG-(v#dN5ldmr7WaEpXxrxY_9
z0#V?J=JK^N`Q&q-Go!kN3t4ChjYI7!RcIb28d!q8nzhJ6gPGJpV=|J(dMd+9=_XsJ
zXhKpJgUafH-}vDBmcBliT7T?wzqEU0-nemztD83#i~Aor{g3~*R}R)&S=^S~T5J{!
zoA+$reb3h3yYqcdKC(0Pp#APY{c5I^9a&(FY3?La&`4d_IJz-y9rcn*bC15TGD!?G
z7f6br4Cc(FTOu9aT@p@1b(NT(e@t9yPC2Tq&`|y*i@BZcW2TtgrPk-U+SN`>#&wk|XZ++!>
zF+TA4z5n8m-`?Llxv|aWHl*GA^n=GYSEtWxZEx2*9)9rq|LPkY{LMFRLFxu?%h?WG
zV{tKinj@0ApeH23v(8F7D}+o33?(ZRfEhE*NE#pp!kOZTOa~H9HxcNK$*d{SNcWN$
z7(Jq?f-UF+qFC)2p)d-v=q5D+Z5>qCiA^!8gv0$CJ+tO;#5{d^C$eEGvmKR;^hF?D
z!6%;koKVGX$)v;RvVjFvDh+KZYlHxYA;1DDLAgqYEG0_=8&Q-UCD8|%67uX4NW(9
z+0ff+*xn{1RMxi+T$LQc`_|SbI0Zcsy-h~B%4+t_4GWS9W(tvRM_1aZ^J6#3vIG^D
zj=k&Ci8D3ZYGxUO>&zlfr5VI0pZz?zAvLl|BMfdZx)l$uJr`mZoPPyE&1?>Z%R&a1
zRVfppj0y?{GZ>WNXbKAJFgstTd+*zM^9L_K{OludefI}#>UZ6}`JK0ywsVd$lw`9k
z{lv!d#Hq7cvlOhRa_jB=P=KpoAuJ3R^?mn}
zW$p*__PMiO2Frf?=H6mwbQUXxA*)6TSh%zIr266B{`TopXjQJ&;qmgMD~d6b#Suz{
zb%!Gq8V**g?F|Es7MU!{fFE8zIC1vG3{_KPkQwr$>$k_9O*M5p8f_C9LACWvGd}V3
zmq?oC@~PjH829$ZC<`PNmHzX4&v0pL+g_-pf4C92bV>
zHqs)DMZ>zSw(7E%1Pcum(t56gKFF0s6$K-E-#@VJ4P=LkLDE2vTYGzVpV+wfq0?vY
zd!H0%b~e8GfBs)@-rm|gRodV~l%tk8g)e0owMyT1W1ieRxi~rA{{Je1lUQ4`^E}M&
z`+jTvd+&4ZIk%}>Evpk7iKNIXl0{M^)flo7MNkv9u#o^UGV(kbCWB1kVMb{No*1wV
zB!Q9$RvINSV5qS`*|I26q$r9kQf!K>Tld!O?>T$#f35F(Q+l57UJS0o+=!Ej;#yV^
zGf~Srp6t#~Rix&agwaf}TSuj~#FFJAue3%kwk_y6U;{`R+jplZopbzHOU
zmQHyFp4@)W+8J}SO~3Zs)x)FJ;i<^uF=SBlV)X4v$wVA)-Pt~SPLbS?+$<60vQPI<
z9&Fn@Vyi>;YgYqnvS%La(F&qeY}?&=FvIeb^<>kUG~^yw;bTRRfVPASU>(WHYBtCP
zVmXPL>CnhxU{e5PYUb)tRp~uOMXF3_*i;py=~=5MXO+OB>_I?$?X$ne^qg~4(F|nU
zokZKT=11?|!Lmgfp|i6y2!ikb@ZRP$P>tZ`#wk*y@7}t1(l-;U)L;0KXV0(hQsdfF
z=jS)BFI88cIWP2UPwoDv|Ms_&m-p}N!Dfh#YtJtiJ!jp*C&lS&h5XZ5Wdx$K0Qk
zjznk8WdTa5TGLS{gi52u3NgwB+M8@7swWFG9Hc7(7AShA%Vcv-QbD4j2pJkV*;CaZ
z1`@N`I9B#8M!5qb!ziFKs$4yD$gCb#5h$9oRaLZNl2wI|L5)52|L~sg^8Y?Fq<&gm>~u=SWX(%NHbWpP&tAzQDG>6
z5`kVEtvCp|2?uI9GB#<)swAz^l9r;W#-!*;hmb%N8dx$*aFkY!W+lh
zi%Gx*0|H8k5+!T(K?qZAG8+TOax0WdNi5Da0=0@<55q8^HZxf$78`&3GrttGRhYR$
z8{tkAI)qO3WC9r`V3S3QG^uRR7|bPGVajmPp_g8{%1z(&%_slP$L_p!!SVRojT^`H
z$?m*;<;&mx>c9Dm=!NRU{PB)u+$yz5OUqL#An%1iWd5
z(rU^EN>E9vq}a?MAl=1j$_3J{LPJ<-Su;!`Dma;%2Hn^T6W)LWNT{jW#MW5jj~Z
zng}?HfMGf21RDX!6HaQA6VO!I71bv*NH>;kzdHU3IH_y(m+_-t2nCj1QPhToRA5Yl%E&;XBAn^q(u%nlDLB~HaNKv9f~!D(xyIRSp`Grt-p>+qfUOHS{bj}Zd^26W$%Gb_rzW9|t_~PwH
z7m2nQ6Sm4#G;>W34JWc;>qQ&3bnk7rnj-zoD`(4gx7%#r`o`Oj582p_f+oC4xFKm4
zI#s5tfEca_LXOE)23y(z6IdZcEi=SAl2JBfRjV*FfFi}rqM=TdW5$S56+==EsDWt|
zQ;dR{6;L_@G3ek_wz4hK#AfiJ=GSNlVNXSX5Zu_&tEz`#
zvUqo4oapZ)HiWbfbq)~(ED48@FM
z7LM!Tn25a{HjA@6%odxrr$gw)@x_ENm_a7e(1fn=)g1zS~G|+Q7a@|R*
z*+AHxFjx~H);^W7$biXKYZ9s^2Z)SxAxt#jI2p~$Y=@Q|Sx^a)Y|LQ=Z3vjtGK$$i
zKq-p0Wvp-js&GsQE_xaPBxH;{3{2*tnv!;uO=6B}4?%Y2bYLE%hljy~S735T6f@+Rp4Y)T{
zk%+2-3^xikqTndj9MP6yIYc`G36`rRZA-R#dEz~WOxuvjsGREJG3TP@HnO^5z(@;R
zqc%yiEC^gHH^d-KaKEl{l~5_*NGw@{pZMf&6bIDJ46A~4iwG&f1`oKC1MW~25Kyw{
z2Bkn3tSPI?ronk~dUExtlZegn-u=rlE*})zin2xIS`Ou`5*=9GGfrE*{hc(Q|rj`V5tCE=OCdhIZ5^76fhSa6ZiWUHp=6jN)
zF$$eXG18XFay2kX8AU5GVWoh{$*F2C{!pQrQgt;6}%-RU+S-RU>p
zc>3A<`onMDTc@-vyUp^!kH7HD%P-%3=gx~CdH=iLc=O`o(GPDO*Gs_N?E>5pm3DJX
zlo~SKQd-huQ*F8!mYELm3$Zjz(vg>tYoRx`>uB
zodz0k1j*{eA#oxmt4ax1vMu&twqyoTRIL{^Ba7&EOAYx*4u0%YzbQjuqlk_`OxMzb
zGLkePHh2mIw7M2@200z%%G`jn9Av`n?CJL8?j?)Ggt06<9uuCRh>=+zetA8)_CntK
z&O7&Rx4d@aJh{Dc_q(^3D_iY1H$U-r|JA?#qar{2$yYx0bAR{i|K^J)yK6u4s&3z^
zwEybs-+S~R9j(_S8eHIH^=d_m9_bbt2v8r;N%XS8(XID??AhD*
z4-bCuXlZfw^a)*04*N&%T*7I3;*ymAAaAT{r)#D9zL-OwSm(-
zaoZ!+NTbS-1*cVJ2#ublRxvm!(}ZNW+hG)tMiK-&Ap>D2z*teigcXV?nufinyBR{Q
zrKp@oqgQW9FB!-E{R?1AjZ5;zl^sYHv-Y$w^?2sl4mpsOp{xwcX89QW*ynyTW`p+5
zpol7Lny@fHPNq<%1OW^cDrOb~FiMosXNW?SQna1C~tp3do$Eq=7^M%ZAn9MjTmn
zY@??@qY3T>$DBq&VD;nifR!0F_xrP(Pn*Nwxc#uMZ^CT}x_3-UE+oomwy__1+P3g4
z$+V}#c<}h*+W8fr5+wvNRD5tTb|*Wpk4K@a7iL0^eOxAA|H5zhxM8I0U`gzBL@W*z
zGBCxkOR=+wC~(#&3Pe-J3^scf#fQ<_Q#a43);1g9_~^lYxAehz>pKtWx*Ct4e`fsT
zfAaVCfB0`7;>FMZWE~rJ=w3j
zwwBwx*q_|EB8+lhAFevzwSwAwxW|>PsGv?T*TLCX$HRWNyPB9V*GI?Ac{31Mj_u^2
zp~(`30dJlh_g8kCp#yC?}iZ%Tdl2fzL8
zr=PlO=PUo&KmN-<`j?OX@|$t(nUDSaF9Ntc)_1<~?Os3rm{#RfA%6Gr@4vCA$
zzH+`BRC8WD+3z+z5e$~uJ6V8~>ogb)tJK067V~kn(+z4=wEg4Dv(qcCa2Ioed)Ic(
z%igy{RxZbRu{|ZS2{ARfR?>&Nhm)J9uuJ{%-CHd;nJOL6p1IajRvwQ|
z<^ItRzVhU=KmYzw^UGiS&KVxosHgwafAlB+!@r1edmksSe&OeDyzhM(+jZpGQ_p_u
z%U^%?N1yq(|L324^UH5l(_wvdvOAg86uG}YPR|yDV(bqWyWPerOqEnZWKC5sfhfi0
z-6vO`+Sl-s+I>X_UAl90J{REXwA)%KC
zI6ad>Dar$GGE9I|gGo1Jna6rhsz8@9Dacdz`+r+?+M
z_uqVc<+;B{-y>Lo|
zIIVc}*p#5wxSs5anc0+FT6L)44quYxG6dDmbR`gjl_o%J{`^6_`cuF7)3^WX
z_Vb^ACHL#x&+mWt-f{on+uyilO0{!KLU4iQEQo}YOH^0P!h}gz8YCFKG&C2xz@g;q
zR0c>ZIRk_%84yqv#DHX!DV5R^X9_Ud=owQkD3MWuI7`sFc)3h3D#)Htw2^MkDU`vg
zp5v0KqEWK;1u28CedbqG%qV3?G|uKWI$6!C<_3#MHLjJYu9nRuGyyJGo6Z;}8<%!c
zS5NxYXRp8d+6UkM=G)Kw$mz4&XZ9ESeYQXRfBrr1{rjK(%nLVOc;yQ}`{J!P-?_N8
z>iLtCjxYW8_s(zJap4=^eDuz7=H9$H
zu^P?{HN)@-_R%Ubsts$waf(#XHBmuEmbE~xEQT;)O}K!?ELt`wt3owWK$Fw14ddv_
z3ia$$s~DWjU?2;m=X7fYQ;?xl3a;d&8-S>qg2I?cRwR?m<7yU-w3xGE$mS#HC9d%yUKesGz5`^}4X{!oW&58wLkwN1Ts>j&Svt$P!v
zSA4HNGVN<8Jbm_NxQ!esG;DwH*1I_ZWH`}m5Ct|D$O^mcCJlH)O{c(+XGNj}(@}CD
zi*U6ncX5za5Q}L@ot3byKqVtsnYE~2I2W-JjUHl8PudB{VI(QA=xJ`@L4=*M44W{g
ztEuK;LOuY>3B&;iW$?96|AuT}O)YXY$5d}oQduID5Tle)2m)lfh?uNKad7n16hHjN
zi-)_HZK&TkKgltdJpaH;o5xR{d;fE1yR+lv`sP3VC%4C=vlq@k^1@ky#y9`qYxf>J{@xES-#yk^CT~98KCSTi+2-W>d6q<#?K<4M_1GEd&PbqH$rMl~
z0^!PJv(RWJ0adK1hAOwoYz}MowU5oTxm4T7=oySTEtbHP=u`oPl-V1ZWncnL%!1*i
z2x0}q*`U=3C{c*YQYUM;m{3fzW2Q-9&T3vqm9rOS1_@78@wHF?YE?s|%n}180;U>~
z;!-ToVamNQEy^V|BvVPq&L*udyz*RZT3sCaiJ}v+{KU`xOc&L4@6KE6yZgK2*2CZX
z-6J>GZY)3d(;wZd;COuJhld}|FaGu0x1U_X?Z)}lTMx$Ey;z~sZNK{LwRG#MzUz0s
zbMN93jG|er08&Om>hLsZM7e?_svLNaB~3W8O+ri?KxDX28&VXJ(=AXg8nyr{P!lO2
z5C#c>v$BsE-7Gi=xdwuCPLZiV7L&BA86XIiD+RfbOt5BVPMb8W!En;x$KUu>IoygR
z({!Wu#DeTxLYR&Uu-uJeOU?x?;fS(}B?vC^g%8|NF*bN~>+sC;oe}SQ`o)j_>}OiU
ztTFodF%05-~XZKo;-Z`*^fT;;)kFAZP^^}-oN+I)ebdesg*6b0MaaABV+2-6-kr>fNEMDFpY}Dq-DXzV4fJ$FrUeqVdvV>0FMQ-x
z4RMn^ltW+mkA7;sd;f>ue(Q%{d;8mOzg;dGKi1yNxN_;slPilR?>~HyYnWF*@CzT~
zTaW(yD-Xx!G~?>73;*MEHbRPX)d|NZCx`@?(tIZ>E!U;6&@+dFvX?9Rgn
z@4V|C*Uzq9-#qKvfARGPe08c<6RX6Yy~6FrjUA4UA6_sar;=igmXVFZdz?%$QqYux
zk<3Uyn#guwb!KDp1XZLAPXew5eX`ObGmJ{Shk(c@X=kxZ0k6o-SYI6-hQbDfwVFvajhX1VzKr+*V3Xy%9#!UZYCHo6)ZBC;gV
zU}Qzt(QF`Sy3A@7MGO`;%f>V@>i_@@`bk7VRLS|x&9d3;&bOcc`JcYn-~QMCG%^Lx^e5lMcv@V?dmT)dwA!;JMUbs5SyH25riPL)>v0hkM}Ni1r$qZ
zTZqAIMvx<`q<|M|sHzyOv^rHpV%<0fLpEU*C$fR00VHUF#R;~mL2p0^M;q*olSWw!
zYIOv;az-iaq6=jZQ*MAMaB6O=9X%J-m@Sti*v)1fGwsS+M_C?OiHax7_9XMb*FOKt
z9?`heK=lbeDRH17Qvjiq6
zy1@pg*$kSFDu#FI#IFEK;
z%4^3mTQb2V2nO7OWkdjlXfa2X*b3E9!=0!G7*uFhfpT*jLQl<@EY)CXk`}bYsKUgi
z6($c&$_y~IaxAE*7#%U?O-HWbKr2_NG?igb_GV9(k`4wk7h)P5|0~Qz^J*
zyJR)6XbF)Hsj@888bh;*L5VCQU1@{G3QOT*0fh}Y?Ff%`Oi0wS!aKV~&1xx=t-Qus
z*_I%Rc}SlIr;Qwn?)7*W6Ft%x}T=|-g{kx_l>*y?23bXt4WCSxEMie3;E
zOcVY5814?!t#&7;u153n(Ua;yMAf*mJ4Mul>+xC}QB)z*eOVO6T#j{MC`xG4wG|Vk
zu|Dj}SxI5;*TZ&MrW|I(W@ZX=93RK_1Qs#Y{bt$PR-h^hf6GtqjNMZ~H!u&6FKbnD
zb?(n^-h|nl_wL_kZrK^FpI$||P>qN*$EY^Oz(8SYZMg-_PrdOUPfw^kG8*5brTeIA
zV~J(X5xo=r80(3ZL1r%;$&HLP*S1k}PSJy85W`%k43muGer`4``aGsN%@B%@_xG-D
zu2!JMZmbW`uDA}sLNFug8kbtOMuBRF{p0P)iA6zK!cZ>E
zs>1QG2e$A&s<-Q>9VKlqE^f_vwiLb1-IXi7kmAMt$8BL~o3XZK%W&j&|L}2-ZMM-i
zY+cU9^5Nafv!_mDqx})CqgXT#mvy>RijA8&Qq5IHQ>_VNVvZHN-A0?6=JLk_`aWRKucgjAvcC~!+;GPo0to^wD{8QBv
z-aPo>!`KLI3F`E$PeTSoXj8H(F{jc{S_N)^hAJXmqz<#T;Go0`a1;>}6$uMYhUftW
zs!&J?v*x;H1#rYv_BE>2oP7O_UqUU_3Szi}=)h3zq!Fqz*~57RH$vF~jWI`c@#v||
z*-n)uU*0=FO+F6j>echmfcv9|_hNPjW*x6xyOyw{=BnfUduH$Aa^oZd0PX(h*4wNd
z0wdS+=b!2@6c68dK(<6v)Y;7wB@1%oqkGvyGiFm=+4TffaY(6iYt5kM5f-?LEw!|2
zCQ-+U)iEO%P&6_c%hp61EC{Tyn>5lDSs5WFb7Nazahd`KSKV;9k_kxC?962su8Qe#Ng|%{VKyG
zW}sC|mDEGI*=|UpT1Bx?nfA0g5Y3n%XsS4a7!wRKqah<=t!jb;PNqn+J7FJ)E-N`5
zYj_K|I9f4l16D<1Hj=6_J2tbgk>N<0DYB`??A2C|W}s7pGb})*B66Y;P`R0_z}8JD`c#f@69?q#%}OD9UCPC=-Th5RI4?;V2Yg{4FVw1Zj?BBNMd%1%tF*RB@VU
z6=9ZVhN56FfQpRr5!$5o^oCxG0OfK6kxnpT)C~8Gs$yG~=q<)^KTBlouJn7ihV!DL
zVJdlSj>*Z_KKaXF5mdEeN#PbuoLpI&I+n~yl&TI1uw-;$O##_J66I>a0xB60Py|R8
zSVrp{6O3YIrGpW2K{mpeq?rwxuI`vlAX0@2wb{(F66gR5?9PFR0t9fF!sw8TXc$QW
zL(pVbnwnJ`s%V!Tpas(?fkvw2u9~)JxmOKGHn+;mXiA%Qh>7M*xzJz>9GFo7l`$im
z3DZ?gam0HJI?!|JLWXlp*r-}Dj%#L^L-2qJS04F^Py8ITEfye_q$nDUt)gEEF)Ro%
z$8iu$B8W*AK{}2^*s_sC$QZ0ff&tQ_)BrN4yh%AQC#|ef2UE677G=XxPQ^@h7h5qS
z14NqWMT4_ou|;Bv!8xI@O+)F~0~kc(EXrmS42p$nLk<^21HvRQO<7bqqZB|=M^<1i
z)dpeZwqin4#Y7MV%P3nist*lix7J=Um4-~CkZ#m0t6kal!6(jb)tZTQOz(jz#9AfW
zg$NWs_WFOwW!t8c!2+5ZED7TFWZ@=IVOH&jtRo97WmF4hf2XKWeGM(NA0!^qpkDUz{BydCYV(KvSm9&0S2)0^^g7nc0vXkF@#q9ThOL2T3Rn7
z`l7?(;0&lR13(c?o>=JQgnKy|4OK3>nMNZ`CcT-MaurpOZ0hLTfo_~;WMz2~GB5{4
zHo8g`#X>?@#kaCk+M3PI}sEJ*?^u+A+C^lfgpadw(jggi-0fef+
z=|!BXRx!aT2OL?GL@@$XO`^F~Wmpr{0FuKFL*Y@1LWvBc7-qq26WIVx)FQ4`3nQ5<
z0fsGT29yAcK~E^)G^Mo=W^0~l2{1<~B55L#M5qu|)0Eo26siKuevxFW(i{r)dFbXJ4f|(=L<^(X3v7?TB?Z^Lv*zSy(6sV{oHbM}E
zO~q`>>DC^jNKa*}-r%W1nP_l!$5HOKSrb8^6L5)wm3U7gTecC!9?IfGMhQ$KDr%E(
zFf)=C-BP2mW=12j;0QWOvlWsFccG$~AXG9+762oW#)3XLO*EBK$!aQ899;`&(ovdy
zbxE^9AWTNOOhlS73pQ*w379>~Q^yD%*_#XRv5FHpIHMxxWh6(`n6B7v7CBnmKfKU+
qYm>*!?pX5z)nQU78pyPr{Qm*7q>-EcNX`cU0000
+
+
+
+
+
+ Original
+
+
+ Hawkeye.VisionBuilder.Workflow.Datatypes.FilePath
+
+
+ Image
+
+
+ Image
+ 1
+ 9500
+ 999999
+ 50
+ 999999
+ 50
+ 999999
+
+
+ Image
+ 3
+ 9500
+ 999999
+ 50
+ 999999
+ 50
+ 999999
+
+
+ Image
+ 4
+ 9500
+ 999999
+ 50
+ 999999
+ 50
+ 999999
+
+
+ Image
+ 5
+ 9500
+ 999999
+ 50
+ 999999
+ 50
+ 999999
+
+
+ Image
+ 6
+ 9500
+ 999999
+ 50
+ 999999
+ 50
+ 999999
+
+
+ Image
+ 7
+ 9500
+ 999999
+ 50
+ 999999
+ 50
+ 999999
+
+
+ Image
+ 8
+ 9500
+ 999999
+ 50
+ 999999
+ 70
+ 999999
+
+
+ Image
+ 14
+ 9500
+ 99999
+ 50
+ 99999
+ 50
+ 99999
+
+
+ Image
+ 2
+ 9500
+ 99999
+ 50
+ 99999
+ 50
+ 99999
+
+
+ Image
+ 10
+ 3000
+ 999999
+ 5
+ 999999
+ 5
+ 999999
+
+
+ Image
+ 11
+ 0
+ 999999
+ 5
+ 999999
+ 5
+ 999999
+
+
+ Image
+ 12
+ 0
+ 999999
+ 5
+ 999999
+ 5
+ 999999
+
+
+ Image
+ 13
+ 4000
+ 999999
+ 0
+ 999999
+ 0
+ 999999
+
+
+ Original
+
+
+ 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
+
\ No newline at end of file
diff --git a/Hawkeye.VisionBuilder.UI.Sources.Emulation/EmulationCameraImageSource.cs b/Hawkeye.VisionBuilder.UI.Sources.Emulation/EmulationCameraImageSource.cs
new file mode 100644
index 0000000..8a2cd72
--- /dev/null
+++ b/Hawkeye.VisionBuilder.UI.Sources.Emulation/EmulationCameraImageSource.cs
@@ -0,0 +1,100 @@
+using OpenCvSharp;
+using System.Drawing;
+using Ninject;
+using VisionBuilder.UI.Common.RecipeProcessing;
+
+namespace Hawkeye.VisionBuilder.UI.Sources.Emulation
+{
+ public class EmulationCameraImageSource:IImageSource, IInitializable
+ {
+ private readonly EmulationSettings _settings;
+ private readonly string _name;
+ private string _currentFolder;
+ private string[] _selectedFiles;
+
+ public EmulationCameraImageSource(EmulationSettings settings)
+ {
+ _settings = settings;
+ }
+
+ public string GetName()
+ {
+ return _name;
+ }
+
+ public async Task GetImage(CancellationToken token)
+ {
+ if (_selectedFiles.Length == 0) return null;
+ var id = (_index) % _selectedFiles.Length;
+ if (id == 0)
+ {
+ if (_index > 0)
+ OnLoopOver();
+ if (_settings.SingleRun && _index > 0)
+ {
+ // infinite sleep
+ await Task.Delay(Timeout.Infinite, token);
+ }
+ Console.WriteLine("starting new emulation cycle");
+ await Task.Delay(_settings.CycleDelay, token);
+ }
+ else
+ {
+ await Task.Delay(_settings.PerImageDelay, token);
+ }
+
+ var path = _selectedFiles[id];
+ _index++;
+
+
+
+ Console.WriteLine($"Getting image {path}");
+
+ return Cv2.ImRead(path);
+ }
+ public void SetFolder(string imagesFolder)
+ {
+ try
+ {
+ _currentFolder = imagesFolder;
+ var path = Path.Combine(@"..\Data\Emulation", imagesFolder);
+ _selectedFiles = Directory.GetFiles(path, "*.bmp")
+ .Concat(Directory.GetFiles(path, "*.png"))
+ .Concat(Directory.GetFiles(path, "*.jpg"))
+ .Where(x => !Path.GetFileNameWithoutExtension(x).Contains("_analysis"))
+ .ToArray();
+ }
+ catch
+ {
+ }
+ }
+
+ public void SetFolderAbsolute(string imagesFolder)
+ {
+ try
+ {
+ _currentFolder = imagesFolder;
+ var path = imagesFolder;
+ _selectedFiles = Directory.GetFiles(path, "*.bmp")
+ .Concat(Directory.GetFiles(path, "*.png"))
+ .Concat(Directory.GetFiles(path, "*.jpg"))
+ .Where(x => !Path.GetFileNameWithoutExtension(x).Contains("_analysis"))
+ .ToArray();
+ }
+ catch
+ {
+ }
+ }
+
+
+ public event Action OnLoopOver = delegate { };
+ private int _index = 0;
+
+
+ public void Initialize()
+ {
+ if(string.IsNullOrEmpty(_settings.EmulationPath)) return;
+ SetFolderAbsolute(_settings.EmulationPath);
+ }
+ }
+}
diff --git a/Hawkeye.VisionBuilder.UI.Sources.Emulation/EmulationSettings.cs b/Hawkeye.VisionBuilder.UI.Sources.Emulation/EmulationSettings.cs
new file mode 100644
index 0000000..17622e3
--- /dev/null
+++ b/Hawkeye.VisionBuilder.UI.Sources.Emulation/EmulationSettings.cs
@@ -0,0 +1,27 @@
+using Inspectron.Settings;
+using VisionBuilder.UI.Common;
+
+namespace Hawkeye.VisionBuilder.UI.Sources.Emulation;
+
+public class EmulationSettings : ISettings
+{
+ public string CameraName { get; }
+
+ public EmulationSettings(string cameraName)
+ {
+ CameraName = cameraName;
+ }
+
+ public int CycleDelay { get; set; } = 1;
+ public int PerImageDelay { get; set; } = 5000;
+ public bool SingleRun { get; set; } = false;
+ public string EmulationPath { get; set; }
+
+ public void RegisterSettings(InspectronSettings settings)
+ {
+ settings.RegisterSimple(this, () => CycleDelay, CameraName+"/Emulation", nameof(CycleDelay));
+ settings.RegisterSimple(this, () => PerImageDelay, CameraName + "/Emulation", nameof(PerImageDelay));
+ settings.RegisterSimple(this, () => SingleRun, CameraName + "/Emulation", nameof(SingleRun));
+ settings.RegisterSimple(this, () => EmulationPath, CameraName + "/Emulation", nameof(EmulationPath));
+ }
+}
\ No newline at end of file
diff --git a/Hawkeye.VisionBuilder.UI.Sources.Emulation/Hawkeye.VisionBuilder.UI.Sources.Emulation.csproj b/Hawkeye.VisionBuilder.UI.Sources.Emulation/Hawkeye.VisionBuilder.UI.Sources.Emulation.csproj
new file mode 100644
index 0000000..d475293
--- /dev/null
+++ b/Hawkeye.VisionBuilder.UI.Sources.Emulation/Hawkeye.VisionBuilder.UI.Sources.Emulation.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/Hawkeye.VisionBuilder.Workflow/Annotations.cs b/Hawkeye.VisionBuilder.Workflow/Annotations.cs
new file mode 100644
index 0000000..04d24ad
--- /dev/null
+++ b/Hawkeye.VisionBuilder.Workflow/Annotations.cs
@@ -0,0 +1,1704 @@
+/* MIT License
+
+Copyright (c) 2016 JetBrains http://www.jetbrains.com
+
+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. */
+
+#nullable disable
+
+
+
+// ReSharper disable UnusedType.Global
+
+#pragma warning disable 1591
+// ReSharper disable UnusedMember.Global
+// ReSharper disable MemberCanBePrivate.Global
+// ReSharper disable UnusedAutoPropertyAccessor.Global
+// ReSharper disable IntroduceOptionalParameters.Global
+// ReSharper disable MemberCanBeProtected.Global
+// ReSharper disable InconsistentNaming
+
+namespace Hawkeye.VisionBuilder.Workflow
+{
+ ///
+ /// Indicates that the value of the marked element could be null sometimes,
+ /// so checking for null is required before its usage.
+ ///
+ ///
+ /// [CanBeNull] object Test() => null;
+ ///
+ /// void UseTest() {
+ /// var p = Test();
+ /// var s = p.ToString(); // Warning: Possible 'System.NullReferenceException'
+ /// }
+ ///
+ [AttributeUsage(
+ AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property |
+ AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event |
+ AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)]
+ public sealed class CanBeNullAttribute : Attribute { }
+
+ ///
+ /// Indicates that the value of the marked element can never be null.
+ ///
+ ///
+ /// [NotNull] object Foo() {
+ /// return null; // Warning: Possible 'null' assignment
+ /// }
+ ///
+ [AttributeUsage(
+ AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property |
+ AttributeTargets.Delegate | AttributeTargets.Field | AttributeTargets.Event |
+ AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.GenericParameter)]
+ public sealed class NotNullAttribute : Attribute { }
+
+ ///
+ /// Can be applied to symbols of types derived from IEnumerable as well as to symbols of Task
+ /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property
+ /// or of the Lazy.Value property can never be null.
+ ///
+ ///
+ /// public void Foo([ItemNotNull]List<string> books)
+ /// {
+ /// foreach (var book in books) {
+ /// if (book != null) // Warning: Expression is always true
+ /// Console.WriteLine(book.ToUpper());
+ /// }
+ /// }
+ ///
+ [AttributeUsage(
+ AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property |
+ AttributeTargets.Delegate | AttributeTargets.Field)]
+ public sealed class ItemNotNullAttribute : Attribute { }
+
+ ///
+ /// Can be applied to symbols of types derived from IEnumerable as well as to symbols of Task
+ /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property
+ /// or of the Lazy.Value property can be null.
+ ///
+ ///
+ /// public void Foo([ItemCanBeNull]List<string> books)
+ /// {
+ /// foreach (var book in books)
+ /// {
+ /// // Warning: Possible 'System.NullReferenceException'
+ /// Console.WriteLine(book.ToUpper());
+ /// }
+ /// }
+ ///
+ [AttributeUsage(
+ AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property |
+ AttributeTargets.Delegate | AttributeTargets.Field)]
+ public sealed class ItemCanBeNullAttribute : Attribute { }
+
+ ///
+ /// Indicates that the marked method builds string by the format pattern and (optional) arguments.
+ /// The parameter, which contains the format string, should be given in the constructor. The format string
+ /// should be in -like form.
+ ///
+ ///
+ /// [StringFormatMethod("message")]
+ /// void ShowError(string message, params object[] args) { /* do something */ }
+ ///
+ /// void Foo() {
+ /// ShowError("Failed: {0}"); // Warning: Non-existing argument in format string
+ /// }
+ ///
+ [AttributeUsage(
+ AttributeTargets.Constructor | AttributeTargets.Method |
+ AttributeTargets.Property | AttributeTargets.Delegate)]
+ public sealed class StringFormatMethodAttribute : Attribute
+ {
+ ///
+ /// Specifies which parameter of an annotated method should be treated as the format string
+ ///
+ public StringFormatMethodAttribute([NotNull] string formatParameterName)
+ {
+ FormatParameterName = formatParameterName;
+ }
+
+ [NotNull] public string FormatParameterName { get; }
+ }
+
+ ///
+ /// Indicates that the marked parameter is a message template where placeholders are to be replaced by the following arguments
+ /// in the order in which they appear
+ ///
+ ///
+ /// void LogInfo([StructuredMessageTemplate]string message, params object[] args) { /* do something */ }
+ ///
+ /// void Foo() {
+ /// LogInfo("User created: {username}"); // Warning: Non-existing argument in format string
+ /// }
+ ///
+ [AttributeUsage(AttributeTargets.Parameter)]
+ public sealed class StructuredMessageTemplateAttribute : Attribute {}
+
+ ///
+ /// Use this annotation to specify a type that contains static or const fields
+ /// with values for the annotated property/field/parameter.
+ /// The specified type will be used to improve completion suggestions.
+ ///
+ ///
+ /// namespace TestNamespace
+ /// {
+ /// public class Constants
+ /// {
+ /// public static int INT_CONST = 1;
+ /// public const string STRING_CONST = "1";
+ /// }
+ ///
+ /// public class Class1
+ /// {
+ /// [ValueProvider("TestNamespace.Constants")] public int myField;
+ /// public void Foo([ValueProvider("TestNamespace.Constants")] string str) { }
+ ///
+ /// public void Test()
+ /// {
+ /// Foo(/*try completion here*/);//
+ /// myField = /*try completion here*/
+ /// }
+ /// }
+ /// }
+ ///
+ [AttributeUsage(
+ AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field,
+ AllowMultiple = true)]
+ public sealed class ValueProviderAttribute : Attribute
+ {
+ public ValueProviderAttribute([NotNull] string name)
+ {
+ Name = name;
+ }
+
+ [NotNull] public string Name { get; }
+ }
+
+ ///
+ /// Indicates that the integral value falls into the specified interval.
+ /// It's allowed to specify multiple non-intersecting intervals.
+ /// Values of interval boundaries are inclusive.
+ ///
+ ///
+ /// void Foo([ValueRange(0, 100)] int value) {
+ /// if (value == -1) { // Warning: Expression is always 'false'
+ /// ...
+ /// }
+ /// }
+ ///
+ [AttributeUsage(
+ AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property |
+ AttributeTargets.Method | AttributeTargets.Delegate,
+ AllowMultiple = true)]
+ public sealed class ValueRangeAttribute : Attribute
+ {
+ public object From { get; }
+ public object To { get; }
+
+ public ValueRangeAttribute(long from, long to)
+ {
+ From = from;
+ To = to;
+ }
+
+ public ValueRangeAttribute(ulong from, ulong to)
+ {
+ From = from;
+ To = to;
+ }
+
+ public ValueRangeAttribute(long value)
+ {
+ From = To = value;
+ }
+
+ public ValueRangeAttribute(ulong value)
+ {
+ From = To = value;
+ }
+ }
+
+ ///
+ /// Indicates that the integral value never falls below zero.
+ ///
+ ///
+ /// void Foo([NonNegativeValue] int value) {
+ /// if (value == -1) { // Warning: Expression is always 'false'
+ /// ...
+ /// }
+ /// }
+ ///
+ [AttributeUsage(
+ AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property |
+ AttributeTargets.Method | AttributeTargets.Delegate)]
+ public sealed class NonNegativeValueAttribute : Attribute { }
+
+ ///
+ /// Indicates that the function argument should be a string literal and match
+ /// one of the parameters of the caller function. This annotation is used for paramerers
+ /// like 'string paramName' parameter of the constuctor.
+ ///
+ ///
+ /// void Foo(string param) {
+ /// if (param == null)
+ /// throw new ArgumentNullException("par"); // Warning: Cannot resolve symbol
+ /// }
+ ///
+ [AttributeUsage(AttributeTargets.Parameter)]
+ public sealed class InvokerParameterNameAttribute : Attribute { }
+
+ ///
+ /// Indicates that the method is contained in a type that implements
+ /// System.ComponentModel.INotifyPropertyChanged interface and this method
+ /// is used to notify that some property value changed.
+ ///
+ ///
+ /// The method should be non-static and conform to one of the supported signatures:
+ ///
+ /// NotifyChanged(string)
+ /// NotifyChanged(params string[])
+ /// NotifyChanged{T}(Expression{Func{T}})
+ /// NotifyChanged{T,U}(Expression{Func{T,U}})
+ /// SetProperty{T}(ref T, T, string)
+ ///
+ ///
+ ///
+ /// public class Foo : INotifyPropertyChanged {
+ /// public event PropertyChangedEventHandler PropertyChanged;
+ ///
+ /// [NotifyPropertyChangedInvocator]
+ /// protected virtual void NotifyChanged(string propertyName) { ... }
+ ///
+ /// string _name;
+ ///
+ /// public string Name {
+ /// get { return _name; }
+ /// set { _name = value; NotifyChanged("LastName"); /* Warning */ }
+ /// }
+ /// }
+ ///
+ /// Examples of generated notifications:
+ ///
+ /// NotifyChanged("Property")
+ /// NotifyChanged(() => Property)
+ /// NotifyChanged((VM x) => x.Property)
+ /// SetProperty(ref myField, value, "Property")
+ ///
+ ///
+ [AttributeUsage(AttributeTargets.Method)]
+ public sealed class NotifyPropertyChangedInvocatorAttribute : Attribute
+ {
+ public NotifyPropertyChangedInvocatorAttribute() { }
+ public NotifyPropertyChangedInvocatorAttribute([NotNull] string parameterName)
+ {
+ ParameterName = parameterName;
+ }
+
+ [CanBeNull] public string ParameterName { get; }
+ }
+
+ ///
+ /// Describes dependency between method input and output.
+ ///
+ ///
+ ///
Function Definition Table syntax:
+ ///
+ /// FDT ::= FDTRow [;FDTRow]*
+ /// FDTRow ::= Input => Output | Output <= Input
+ /// Input ::= ParameterName: Value [, Input]*
+ /// Output ::= [ParameterName: Value]* {halt|stop|void|nothing|Value}
+ /// Value ::= true | false | null | notnull | canbenull
+ ///
+ /// If the method has a single input parameter, its name could be omitted.
+ /// Using halt (or void/nothing, which is the same) for the method output
+ /// means that the method doesn't return normally (throws or terminates the process).
+ /// Value canbenull is only applicable for output parameters.
+ /// You can use multiple [ContractAnnotation] for each FDT row, or use single attribute
+ /// with rows separated by the semicolon. There is no notion of order rows, all rows are checked
+ /// for applicability and applied per each program state tracked by the analysis engine.
+ ///
+ ///
+ ///
+ /// [ContractAnnotation("=> halt")]
+ /// public void TerminationMethod()
+ ///
+ ///
+ /// [ContractAnnotation("null <= param:null")] // reverse condition syntax
+ /// public string GetName(string surname)
+ ///
+ ///
+ /// [ContractAnnotation("s:null => true")]
+ /// public bool IsNullOrEmpty(string s) // string.IsNullOrEmpty()
+ ///
+ ///
+ /// // A method that returns null if the parameter is null,
+ /// // and not null if the parameter is not null
+ /// [ContractAnnotation("null => null; notnull => notnull")]
+ /// public object Transform(object data)
+ ///
+ ///
+ /// [ContractAnnotation("=> true, result: notnull; => false, result: null")]
+ /// public bool TryParse(string s, out Person result)
+ ///
+ ///
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
+ public sealed class ContractAnnotationAttribute : Attribute
+ {
+ public ContractAnnotationAttribute([NotNull] string contract)
+ : this(contract, false) { }
+
+ public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates)
+ {
+ Contract = contract;
+ ForceFullStates = forceFullStates;
+ }
+
+ [NotNull] public string Contract { get; }
+
+ public bool ForceFullStates { get; }
+ }
+
+ ///
+ /// Indicates whether the marked element should be localized.
+ ///
+ ///
+ /// [LocalizationRequiredAttribute(true)]
+ /// class Foo {
+ /// string str = "my string"; // Warning: Localizable string
+ /// }
+ ///
+ [AttributeUsage(AttributeTargets.All)]
+ public sealed class LocalizationRequiredAttribute : Attribute
+ {
+ public LocalizationRequiredAttribute() : this(true) { }
+
+ public LocalizationRequiredAttribute(bool required)
+ {
+ Required = required;
+ }
+
+ public bool Required { get; }
+ }
+
+ ///
+ /// Indicates that the value of the marked type (or its derivatives)
+ /// cannot be compared using '==' or '!=' operators and Equals()
+ /// should be used instead. However, using '==' or '!=' for comparison
+ /// with null is always permitted.
+ ///
+ ///
+ /// [CannotApplyEqualityOperator]
+ /// class NoEquality { }
+ ///
+ /// class UsesNoEquality {
+ /// void Test() {
+ /// var ca1 = new NoEquality();
+ /// var ca2 = new NoEquality();
+ /// if (ca1 != null) { // OK
+ /// bool condition = ca1 == ca2; // Warning
+ /// }
+ /// }
+ /// }
+ ///
+ [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct)]
+ public sealed class CannotApplyEqualityOperatorAttribute : Attribute { }
+
+ ///
+ /// When applied to a target attribute, specifies a requirement for any type marked
+ /// with the target attribute to implement or inherit specific type or types.
+ ///
+ ///
+ /// [BaseTypeRequired(typeof(IComponent)] // Specify requirement
+ /// class ComponentAttribute : Attribute { }
+ ///
+ /// [Component] // ComponentAttribute requires implementing IComponent interface
+ /// class MyComponent : IComponent { }
+ ///
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
+ [BaseTypeRequired(typeof(Attribute))]
+ public sealed class BaseTypeRequiredAttribute : Attribute
+ {
+ public BaseTypeRequiredAttribute([NotNull] Type baseType)
+ {
+ BaseType = baseType;
+ }
+
+ [NotNull] public Type BaseType { get; }
+ }
+
+ ///
+ /// Indicates that the marked symbol is used implicitly (e.g. via reflection, in external library),
+ /// so this symbol will be ignored by usage-checking inspections.
+ /// You can use and
+ /// to configure how this attribute is applied.
+ ///
+ ///
+ /// [UsedImplicitly]
+ /// public class TypeConverter {}
+ ///
+ /// public class SummaryData
+ /// {
+ /// [UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)]
+ /// public SummaryData() {}
+ /// }
+ ///
+ /// [UsedImplicitly(ImplicitUseTargetFlags.WithInheritors | ImplicitUseTargetFlags.Default)]
+ /// public interface IService {}
+ ///
+ [AttributeUsage(AttributeTargets.All)]
+ public sealed class UsedImplicitlyAttribute : Attribute
+ {
+ public UsedImplicitlyAttribute()
+ : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { }
+
+ public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags)
+ : this(useKindFlags, ImplicitUseTargetFlags.Default) { }
+
+ public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags)
+ : this(ImplicitUseKindFlags.Default, targetFlags) { }
+
+ public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags)
+ {
+ UseKindFlags = useKindFlags;
+ TargetFlags = targetFlags;
+ }
+
+ public ImplicitUseKindFlags UseKindFlags { get; }
+
+ public ImplicitUseTargetFlags TargetFlags { get; }
+ }
+
+ ///
+ /// Can be applied to attributes, type parameters, and parameters of a type assignable from .
+ /// When applied to an attribute, the decorated attribute behaves the same as .
+ /// When applied to a type parameter or to a parameter of type ,
+ /// indicates that the corresponding type is used implicitly.
+ ///
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.GenericParameter | AttributeTargets.Parameter)]
+ public sealed class MeansImplicitUseAttribute : Attribute
+ {
+ public MeansImplicitUseAttribute()
+ : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { }
+
+ public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags)
+ : this(useKindFlags, ImplicitUseTargetFlags.Default) { }
+
+ public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags)
+ : this(ImplicitUseKindFlags.Default, targetFlags) { }
+
+ public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags)
+ {
+ UseKindFlags = useKindFlags;
+ TargetFlags = targetFlags;
+ }
+
+ [UsedImplicitly] public ImplicitUseKindFlags UseKindFlags { get; }
+
+ [UsedImplicitly] public ImplicitUseTargetFlags TargetFlags { get; }
+ }
+
+ ///
+ /// Specifies the details of implicitly used symbol when it is marked
+ /// with or .
+ ///
+ [Flags]
+ public enum ImplicitUseKindFlags
+ {
+ Default = Access | Assign | InstantiatedWithFixedConstructorSignature,
+ /// Only entity marked with attribute considered used.
+ Access = 1,
+ /// Indicates implicit assignment to a member.
+ Assign = 2,
+ ///
+ /// Indicates implicit instantiation of a type with fixed constructor signature.
+ /// That means any unused constructor parameters won't be reported as such.
+ ///
+ InstantiatedWithFixedConstructorSignature = 4,
+ /// Indicates implicit instantiation of a type.
+ InstantiatedNoFixedConstructorSignature = 8,
+ }
+
+ ///
+ /// Specifies what is considered to be used implicitly when marked
+ /// with or .
+ ///
+ [Flags]
+ public enum ImplicitUseTargetFlags
+ {
+ Default = Itself,
+ Itself = 1,
+ /// Members of the type marked with the attribute are considered used.
+ Members = 2,
+ /// Inherited entities are considered used.
+ WithInheritors = 4,
+ /// Entity marked with the attribute and all its members considered used.
+ WithMembers = Itself | Members
+ }
+
+ ///
+ /// This attribute is intended to mark publicly available API,
+ /// which should not be removed and so is treated as used.
+ ///
+ [MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)]
+ [AttributeUsage(AttributeTargets.All, Inherited = false)]
+ public sealed class PublicAPIAttribute : Attribute
+ {
+ public PublicAPIAttribute() { }
+
+ public PublicAPIAttribute([NotNull] string comment)
+ {
+ Comment = comment;
+ }
+
+ [CanBeNull] public string Comment { get; }
+ }
+
+ ///
+ /// Tells the code analysis engine if the parameter is completely handled when the invoked method is on stack.
+ /// If the parameter is a delegate, indicates that delegate can only be invoked during method execution
+ /// (the delegate can be invoked zero or multiple times, but not stored to some field and invoked later,
+ /// when the containing method is no longer on the execution stack).
+ /// If the parameter is an enumerable, indicates that it is enumerated while the method is executed.
+ /// If is true, the attribute will only takes effect if the method invocation is located under the 'await' expression.
+ ///
+ [AttributeUsage(AttributeTargets.Parameter)]
+ public sealed class InstantHandleAttribute : Attribute
+ {
+ ///
+ /// Require the method invocation to be used under the 'await' expression for this attribute to take effect on code analysis engine.
+ /// Can be used for delegate/enumerable parameters of 'async' methods.
+ ///
+ public bool RequireAwait { get; set; }
+ }
+
+ ///
+ /// Indicates that a method does not make any observable state changes.
+ /// The same as System.Diagnostics.Contracts.PureAttribute.
+ ///
+ ///
+ /// [Pure] int Multiply(int x, int y) => x * y;
+ ///
+ /// void M() {
+ /// Multiply(123, 42); // Warning: Return value of pure method is not used
+ /// }
+ ///
+ [AttributeUsage(AttributeTargets.Method)]
+ public sealed class PureAttribute : Attribute { }
+
+ ///
+ /// Indicates that the return value of the method invocation must be used.
+ ///
+ ///
+ /// Methods decorated with this attribute (in contrast to pure methods) might change state,
+ /// but make no sense without using their return value.
+ /// Similarly to , this attribute
+ /// will help to detect usages of the method when the return value is not used.
+ /// Optionally, you can specify a message to use when showing warnings, e.g.
+ /// [MustUseReturnValue("Use the return value to...")].
+ ///
+ [AttributeUsage(AttributeTargets.Method)]
+ public sealed class MustUseReturnValueAttribute : Attribute
+ {
+ public MustUseReturnValueAttribute() { }
+
+ public MustUseReturnValueAttribute([NotNull] string justification)
+ {
+ Justification = justification;
+ }
+
+ [CanBeNull] public string Justification { get; }
+ }
+
+ ///
+ /// This annotation allows to enforce allocation-less usage patterns of delegates for performance-critical APIs.
+ /// When this annotation is applied to the parameter of delegate type, IDE checks the input argument of this parameter:
+ /// * When lambda expression or anonymous method is passed as an argument, IDE verifies that the passed closure
+ /// has no captures of the containing local variables and the compiler is able to cache the delegate instance
+ /// to avoid heap allocations. Otherwise the warning is produced.
+ /// * IDE warns when method name or local function name is passed as an argument as this always results
+ /// in heap allocation of the delegate instance.
+ ///
+ ///
+ /// In C# 9.0 code IDE would also suggest to annotate the anonymous function with 'static' modifier
+ /// to make use of the similar analysis provided by the language/compiler.
+ ///
+ [AttributeUsage(AttributeTargets.Parameter)]
+ public sealed class RequireStaticDelegateAttribute : Attribute
+ {
+ public bool IsError { get; set; }
+ }
+
+ ///
+ /// Indicates the type member or parameter of some type, that should be used instead of all other ways
+ /// to get the value of that type. This annotation is useful when you have some "context" value evaluated
+ /// and stored somewhere, meaning that all other ways to get this value must be consolidated with existing one.
+ ///
+ ///
+ /// class Foo {
+ /// [ProvidesContext] IBarService _barService = ...;
+ ///
+ /// void ProcessNode(INode node) {
+ /// DoSomething(node, node.GetGlobalServices().Bar);
+ /// // ^ Warning: use value of '_barService' field
+ /// }
+ /// }
+ ///
+ [AttributeUsage(
+ AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.Method |
+ AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.GenericParameter)]
+ public sealed class ProvidesContextAttribute : Attribute { }
+
+ ///
+ /// Indicates that a parameter is a path to a file or a folder within a web project.
+ /// Path can be relative or absolute, starting from web root (~).
+ ///
+ [AttributeUsage(AttributeTargets.Parameter)]
+ public sealed class PathReferenceAttribute : Attribute
+ {
+ public PathReferenceAttribute() { }
+
+ public PathReferenceAttribute([NotNull, PathReference] string basePath)
+ {
+ BasePath = basePath;
+ }
+
+ [CanBeNull] public string BasePath { get; }
+ }
+
+ ///
+ /// An extension method marked with this attribute is processed by code completion
+ /// as a 'Source Template'. When the extension method is completed over some expression, its source code
+ /// is automatically expanded like a template at call site.
+ ///
+ ///
+ /// Template method body can contain valid source code and/or special comments starting with '$'.
+ /// Text inside these comments is added as source code when the template is applied. Template parameters
+ /// can be used either as additional method parameters or as identifiers wrapped in two '$' signs.
+ /// Use the attribute to specify macros for parameters.
+ ///
+ ///
+ /// In this example, the 'forEach' method is a source template available over all values
+ /// of enumerable types, producing ordinary C# 'foreach' statement and placing caret inside block:
+ ///
+ /// [SourceTemplate]
+ /// public static void forEach<T>(this IEnumerable<T> xs) {
+ /// foreach (var x in xs) {
+ /// //$ $END$
+ /// }
+ /// }
+ ///
+ ///
+ [AttributeUsage(AttributeTargets.Method)]
+ public sealed class SourceTemplateAttribute : Attribute { }
+
+ ///
+ /// Allows specifying a macro for a parameter of a source template.
+ ///
+ ///
+ /// You can apply the attribute on the whole method or on any of its additional parameters. The macro expression
+ /// is defined in the property. When applied on a method, the target
+ /// template parameter is defined in the property. To apply the macro silently
+ /// for the parameter, set the property value = -1.
+ ///
+ ///
+ /// Applying the attribute on a source template method:
+ ///
+ /// [SourceTemplate, Macro(Target = "item", Expression = "suggestVariableName()")]
+ /// public static void forEach<T>(this IEnumerable<T> collection) {
+ /// foreach (var item in collection) {
+ /// //$ $END$
+ /// }
+ /// }
+ ///
+ /// Applying the attribute on a template method parameter:
+ ///
+ /// [SourceTemplate]
+ /// public static void something(this Entity x, [Macro(Expression = "guid()", Editable = -1)] string newguid) {
+ /// /*$ var $x$Id = "$newguid$" + x.ToString();
+ /// x.DoSomething($x$Id); */
+ /// }
+ ///
+ ///
+ [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method, AllowMultiple = true)]
+ public sealed class MacroAttribute : Attribute
+ {
+ ///
+ /// Allows specifying a macro that will be executed for a source template
+ /// parameter when the template is expanded.
+ ///
+ [CanBeNull] public string Expression { get; set; }
+
+ ///
+ /// Allows specifying which occurrence of the target parameter becomes editable when the template is deployed.
+ ///
+ ///
+ /// If the target parameter is used several times in the template, only one occurrence becomes editable;
+ /// other occurrences are changed synchronously. To specify the zero-based index of the editable occurrence,
+ /// use values >= 0. To make the parameter non-editable when the template is expanded, use -1.
+ ///
+ public int Editable { get; set; }
+
+ ///
+ /// Identifies the target parameter of a source template if the
+ /// is applied on a template method.
+ ///
+ [CanBeNull] public string Target { get; set; }
+ }
+
+ ///
+ /// Indicates how method, constructor invocation, or property access
+ /// over collection type affects the contents of the collection.
+ /// When applied to a return value of a method indicates if the returned collection
+ /// is created exclusively for the caller (CollectionAccessType.UpdatedContent) or
+ /// can be read/updated from outside (CollectionAccessType.Read | CollectionAccessType.UpdatedContent)
+ /// Use to specify the access type.
+ ///
+ ///
+ /// Using this attribute only makes sense if all collection methods are marked with this attribute.
+ ///
+ ///
+ /// public class MyStringCollection : List<string>
+ /// {
+ /// [CollectionAccess(CollectionAccessType.Read)]
+ /// public string GetFirstString()
+ /// {
+ /// return this.ElementAt(0);
+ /// }
+ /// }
+ /// class Test
+ /// {
+ /// public void Foo()
+ /// {
+ /// // Warning: Contents of the collection is never updated
+ /// var col = new MyStringCollection();
+ /// string x = col.GetFirstString();
+ /// }
+ /// }
+ ///
+ [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property | AttributeTargets.ReturnValue)]
+ public sealed class CollectionAccessAttribute : Attribute
+ {
+ public CollectionAccessAttribute(CollectionAccessType collectionAccessType)
+ {
+ CollectionAccessType = collectionAccessType;
+ }
+
+ public CollectionAccessType CollectionAccessType { get; }
+ }
+
+ ///
+ /// Provides a value for the to define
+ /// how the collection method invocation affects the contents of the collection.
+ ///
+ [Flags]
+ public enum CollectionAccessType
+ {
+ /// Method does not use or modify content of the collection.
+ None = 0,
+ /// Method only reads content of the collection but does not modify it.
+ Read = 1,
+ /// Method can change content of the collection but does not add new elements.
+ ModifyExistingContent = 2,
+ /// Method can add new elements to the collection.
+ UpdatedContent = ModifyExistingContent | 4
+ }
+
+ ///
+ /// Indicates that the marked method is assertion method, i.e. it halts the control flow if
+ /// one of the conditions is satisfied. To set the condition, mark one of the parameters with
+ /// attribute.
+ ///
+ [AttributeUsage(AttributeTargets.Method)]
+ public sealed class AssertionMethodAttribute : Attribute { }
+
+ ///
+ /// Indicates the condition parameter of the assertion method. The method itself should be
+ /// marked by attribute. The mandatory argument of
+ /// the attribute is the assertion type.
+ ///
+ [AttributeUsage(AttributeTargets.Parameter)]
+ public sealed class AssertionConditionAttribute : Attribute
+ {
+ public AssertionConditionAttribute(AssertionConditionType conditionType)
+ {
+ ConditionType = conditionType;
+ }
+
+ public AssertionConditionType ConditionType { get; }
+ }
+
+ ///
+ /// Specifies assertion type. If the assertion method argument satisfies the condition,
+ /// then the execution continues. Otherwise, execution is assumed to be halted.
+ ///
+ public enum AssertionConditionType
+ {
+ /// Marked parameter should be evaluated to true.
+ IS_TRUE = 0,
+ /// Marked parameter should be evaluated to false.
+ IS_FALSE = 1,
+ /// Marked parameter should be evaluated to null value.
+ IS_NULL = 2,
+ /// Marked parameter should be evaluated to not null value.
+ IS_NOT_NULL = 3,
+ }
+
+ ///
+ /// Indicates that the marked method unconditionally terminates control flow execution.
+ /// For example, it could unconditionally throw exception.
+ ///
+ [Obsolete("Use [ContractAnnotation('=> halt')] instead")]
+ [AttributeUsage(AttributeTargets.Method)]
+ public sealed class TerminatesProgramAttribute : Attribute { }
+
+ ///
+ /// Indicates that the method is a pure LINQ method, with postponed enumeration (like Enumerable.Select,
+ /// .Where). This annotation allows inference of [InstantHandle] annotation for parameters
+ /// of delegate type by analyzing LINQ method chains.
+ ///
+ [AttributeUsage(AttributeTargets.Method)]
+ public sealed class LinqTunnelAttribute : Attribute { }
+
+ ///
+ /// Indicates that IEnumerable passed as a parameter is not enumerated.
+ /// Use this annotation to suppress the 'Possible multiple enumeration of IEnumerable' inspection.
+ ///
+ ///
+ /// static void ThrowIfNull<T>([NoEnumeration] T v, string n) where T : class
+ /// {
+ /// // custom check for null but no enumeration
+ /// }
+ ///
+ /// void Foo(IEnumerable<string> values)
+ /// {
+ /// ThrowIfNull(values, nameof(values));
+ /// var x = values.ToList(); // No warnings about multiple enumeration
+ /// }
+ ///
+ [AttributeUsage(AttributeTargets.Parameter)]
+ public sealed class NoEnumerationAttribute : Attribute { }
+
+ ///
+ /// Indicates that the marked parameter, field, or property is a regular expression pattern.
+ ///
+ [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)]
+ public sealed class RegexPatternAttribute : Attribute { }
+
+ ///
+ /// Language of injected code fragment inside marked by string literal.
+ ///
+ public enum InjectedLanguage
+ {
+ CSS,
+ HTML,
+ JAVASCRIPT,
+ JSON,
+ XML
+ }
+
+ ///
+ /// Indicates that the marked parameter, field, or property is accepting a string literal
+ /// containing code fragment in a language specified by the .
+ ///
+ ///
+ /// void Foo([LanguageInjection(InjectedLanguage.CSS, Prefix = "body{", Suffix = "}")] string cssProps)
+ /// {
+ /// // cssProps should only contains a list of CSS properties
+ /// }
+ ///
+ [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)]
+ public sealed class LanguageInjectionAttribute : Attribute
+ {
+ public LanguageInjectionAttribute(InjectedLanguage injectedLanguage)
+ {
+ InjectedLanguage = injectedLanguage;
+ }
+
+ /// Specify a language of injected code fragment.
+ public InjectedLanguage InjectedLanguage { get; }
+
+ /// Specify a string that "precedes" injected string literal.
+ [CanBeNull] public string Prefix { get; set; }
+
+ /// Specify a string that "follows" injected string literal.
+ [CanBeNull] public string Suffix { get; set; }
+ }
+
+ ///
+ /// Prevents the Member Reordering feature from tossing members of the marked class.
+ ///
+ ///
+ /// The attribute must be mentioned in your member reordering patterns.
+ ///
+ [AttributeUsage(
+ AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.Enum)]
+ public sealed class NoReorderAttribute : Attribute { }
+
+ ///
+ ///
+ /// Defines the code search template using the Structural Search and Replace syntax.
+ /// It allows you to find and, if necessary, replace blocks of code that match a specific pattern.
+ /// Search and replace patterns consist of a textual part and placeholders.
+ /// Textural part must contain only identifiers allowed in the target language and will be matched exactly (white spaces, tabulation characters, and line breaks are ignored).
+ /// Placeholders allow matching variable parts of the target code blocks.
+ /// A placeholder has the following format: $placeholder_name$- where placeholder_name is an arbitrary identifier.
+ ///
+ ///
+ /// Available placeholders:
+ ///
+ /// $this$ - expression of containing type
+ /// $thisType$ - containing type
+ /// $member$ - current member placeholder
+ /// $qualifier$ - this placeholder is available in the replace pattern and can be used to insert qualifier expression matched by the $member$ placeholder.
+ /// (Note that if $qualifier$ placeholder is used, then $member$ placeholder will match only qualified references)
+ /// $expression$ - expression of any type
+ /// $identifier$ - identifier placeholder
+ /// $args$ - any number of arguments
+ /// $arg$ - single argument
+ /// $arg1$ ... $arg10$ - single argument
+ /// $stmts$ - any number of statements
+ /// $stmt$ - single statement
+ /// $stmt1$ ... $stmt10$ - single statement
+ /// $name{Expression, 'Namespace.FooType'}$ - expression with 'Namespace.FooType' type
+ /// $expression{'Namespace.FooType'}$ - expression with 'Namespace.FooType' type
+ /// $name{Type, 'Namespace.FooType'}$ - 'Namespace.FooType' type
+ /// $type{'Namespace.FooType'}$ - 'Namespace.FooType' type
+ /// $statement{1,2}$ - 1 or 2 statements
+ ///
+ ///
+ ///
+ /// Note that you can also define your own placeholders of the supported types and specify arguments for each placeholder type.
+ /// This can be done using the following format: $name{type, arguments}$. Where 'name' - is the name of your placeholder,
+ /// 'type' - is the type of your placeholder (one of the following: Expression, Type, Identifier, Statement, Argument, Member),
+ /// 'arguments' - arguments list for your placeholder. Each placeholder type supports it's own arguments, check examples below for mode details.
+ /// Placeholder type may be omitted and determined from the placeholder name, if name has one of the following prefixes:
+ ///
+ /// expr, expression - expression placeholder, e.g. $exprPlaceholder{}$, $expressionFoo{}$
+ /// arg, argument - argument placeholder, e.g. $argPlaceholder{}$, $argumentFoo{}$
+ /// ident, identifier - identifier placeholder, e.g. $identPlaceholder{}$, $identifierFoo{}$
+ /// stmt, statement - statement placeholder, e.g. $stmtPlaceholder{}$, $statementFoo{}$
+ /// type - type placeholder, e.g. $typePlaceholder{}$, $typeFoo{}$
+ /// member - member placeholder, e.g. $memberPlaceholder{}$, $memberFoo{}$
+ ///
+ ///
+ ///
+ /// Expression placeholder arguments:
+ ///
+ /// expressionType - string value in single quotes, specifies full type name to match (empty string by default)
+ /// exactType - boolean value, specifies if expression should have exact type match (false by default)
+ ///
+ /// Examples:
+ ///
+ /// $myExpr{Expression, 'Namespace.FooType', true}$ - defines expression placeholder, matching expressions of the 'Namespace.FooType' type with exact matching.
+ /// $myExpr{Expression, 'Namespace.FooType'}$ - defines expression placeholder, matching expressions of the 'Namespace.FooType' type or expressions which can be implicitly converted to 'Namespace.FooType'.
+ /// $myExpr{Expression}$ - defines expression placeholder, matching expressions of any type.
+ /// $exprFoo{'Namespace.FooType', true}$ - defines expression placeholder, matching expressions of the 'Namespace.FooType' type with exact matching.
+ ///
+ ///
+ ///
+ /// Type placeholder arguments:
+ ///
+ /// type - string value in single quotes, specifies full type name to match (empty string by default)
+ /// exactType - boolean value, specifies if expression should have exact type match (false by default)
+ ///
+ /// Examples:
+ ///
+ /// $myType{Type, 'Namespace.FooType', true}$ - defines type placeholder, matching 'Namespace.FooType' types with exact matching.
+ /// $myType{Type, 'Namespace.FooType'}$ - defines type placeholder, matching 'Namespace.FooType' types or types, which can be implicitly converted to 'Namespace.FooType'.
+ /// $myType{Type}$ - defines type placeholder, matching any type.
+ /// $typeFoo{'Namespace.FooType', true}$ - defines types placeholder, matching 'Namespace.FooType' types with exact matching.
+ ///
+ ///
+ ///
+ /// Identifier placeholder arguments:
+ ///
+ /// nameRegex - string value in single quotes, specifies regex to use for matching (empty string by default)
+ /// nameRegexCaseSensitive - boolean value, specifies if name regex is case sensitive (true by default)
+ /// type - string value in single quotes, specifies full type name to match (empty string by default)
+ /// exactType - boolean value, specifies if expression should have exact type match (false by default)
+ ///
+ /// Examples:
+ ///
+ /// $myIdentifier{Identifier, 'my.*', false, 'Namespace.FooType', true}$ - defines identifier placeholder, matching identifiers (ignoring case) starting with 'my' prefix with 'Namespace.FooType' type.
+ /// $myIdentifier{Identifier, 'my.*', true, 'Namespace.FooType', true}$ - defines identifier placeholder, matching identifiers (case sensitively) starting with 'my' prefix with 'Namespace.FooType' type.
+ /// $identFoo{'my.*'}$ - defines identifier placeholder, matching identifiers (case sensitively) starting with 'my' prefix.
+ ///
+ ///
+ ///
+ /// Statement placeholder arguments:
+ ///
+ /// minimalOccurrences - minimal number of statements to match (-1 by default)
+ /// maximalOccurrences - maximal number of statements to match (-1 by default)
+ ///
+ /// Examples:
+ ///
+ /// $myStmt{Statement, 1, 2}$ - defines statement placeholder, matching 1 or 2 statements.
+ /// $myStmt{Statement}$ - defines statement placeholder, matching any number of statements.
+ /// $stmtFoo{1, 2}$ - defines statement placeholder, matching 1 or 2 statements.
+ ///
+ ///
+ ///
+ /// Argument placeholder arguments:
+ ///
+ /// minimalOccurrences - minimal number of arguments to match (-1 by default)
+ /// maximalOccurrences - maximal number of arguments to match (-1 by default)
+ ///
+ /// Examples:
+ ///
+ /// $myArg{Argument, 1, 2}$ - defines argument placeholder, matching 1 or 2 arguments.
+ /// $myArg{Argument}$ - defines argument placeholder, matching any number of arguments.
+ /// $argFoo{1, 2}$ - defines argument placeholder, matching 1 or 2 arguments.
+ ///
+ ///
+ ///
+ /// Member placeholder arguments:
+ ///
+ /// docId - string value in single quotes, specifies XML documentation id of the member to match (empty by default)
+ ///
+ /// Examples:
+ ///
+ /// $myMember{Member, 'M:System.String.IsNullOrEmpty(System.String)'}$ - defines member placeholder, matching 'IsNullOrEmpty' member of the 'System.String' type.
+ /// $memberFoo{'M:System.String.IsNullOrEmpty(System.String)'}$ - defines member placeholder, matching 'IsNullOrEmpty' member of the 'System.String' type.
+ ///
+ ///
+ ///
+ /// For more information please refer to the Structural Search and Replace article.
+ ///
+ ///
+ [AttributeUsage(
+ AttributeTargets.Method
+ | AttributeTargets.Constructor
+ | AttributeTargets.Property
+ | AttributeTargets.Field
+ | AttributeTargets.Event
+ | AttributeTargets.Interface
+ | AttributeTargets.Class
+ | AttributeTargets.Struct
+ | AttributeTargets.Enum,
+ AllowMultiple = true,
+ Inherited = false)]
+ public sealed class CodeTemplateAttribute : Attribute
+ {
+ public CodeTemplateAttribute(string searchTemplate)
+ {
+ SearchTemplate = searchTemplate;
+ }
+
+ ///
+ /// Structural search pattern to use in the code template.
+ /// Pattern includes textual part, which must contain only identifiers allowed in the target language,
+ /// and placeholders, which allow matching variable parts of the target code blocks.
+ ///
+ public string SearchTemplate { get; }
+
+ ///
+ /// Message to show when the search pattern was found.
+ /// You can also prepend the message text with "Error:", "Warning:", "Suggestion:" or "Hint:" prefix to specify the pattern severity.
+ /// Code patterns with replace template produce suggestions by default.
+ /// However, if replace template is not provided, then warning severity will be used.
+ ///
+ public string Message { get; set; }
+
+ ///
+ /// Structural search replace pattern to use in code template replacement.
+ ///
+ public string ReplaceTemplate { get; set; }
+
+ ///
+ /// Replace message to show in the light bulb.
+ ///
+ public string ReplaceMessage { get; set; }
+
+ ///
+ /// Apply code formatting after code replacement.
+ ///
+ public bool FormatAfterReplace { get; set; } = true;
+
+ ///
+ /// Whether similar code blocks should be matched.
+ ///
+ public bool MatchSimilarConstructs { get; set; }
+
+ ///
+ /// Automatically insert namespace import directives or remove qualifiers that become redundant after the template is applied.
+ ///
+ public bool ShortenReferences { get; set; }
+
+ ///
+ /// String to use as a suppression key.
+ /// By default the following suppression key is used 'CodeTemplate_SomeType_SomeMember',
+ /// where 'SomeType' and 'SomeMember' are names of the associated containing type and member to which this attribute is applied.
+ ///
+ public string SuppressionKey { get; set; }
+ }
+
+ #region ASP.NET
+
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
+ public sealed class AspChildControlTypeAttribute : Attribute
+ {
+ public AspChildControlTypeAttribute([NotNull] string tagName, [NotNull] Type controlType)
+ {
+ TagName = tagName;
+ ControlType = controlType;
+ }
+
+ [NotNull] public string TagName { get; }
+
+ [NotNull] public Type ControlType { get; }
+ }
+
+ [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)]
+ public sealed class AspDataFieldAttribute : Attribute { }
+
+ [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)]
+ public sealed class AspDataFieldsAttribute : Attribute { }
+
+ [AttributeUsage(AttributeTargets.Property)]
+ public sealed class AspMethodPropertyAttribute : Attribute { }
+
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
+ public sealed class AspRequiredAttributeAttribute : Attribute
+ {
+ public AspRequiredAttributeAttribute([NotNull] string attribute)
+ {
+ Attribute = attribute;
+ }
+
+ [NotNull] public string Attribute { get; }
+ }
+
+ [AttributeUsage(AttributeTargets.Property)]
+ public sealed class AspTypePropertyAttribute : Attribute
+ {
+ public bool CreateConstructorReferences { get; }
+
+ public AspTypePropertyAttribute(bool createConstructorReferences)
+ {
+ CreateConstructorReferences = createConstructorReferences;
+ }
+ }
+
+ #endregion
+
+ #region ASP.NET MVC
+
+ [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
+ public sealed class AspMvcAreaMasterLocationFormatAttribute : Attribute
+ {
+ public AspMvcAreaMasterLocationFormatAttribute([NotNull] string format)
+ {
+ Format = format;
+ }
+
+ [NotNull] public string Format { get; }
+ }
+
+ [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
+ public sealed class AspMvcAreaPartialViewLocationFormatAttribute : Attribute
+ {
+ public AspMvcAreaPartialViewLocationFormatAttribute([NotNull] string format)
+ {
+ Format = format;
+ }
+
+ [NotNull] public string Format { get; }
+ }
+
+ [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
+ public sealed class AspMvcAreaViewLocationFormatAttribute : Attribute
+ {
+ public AspMvcAreaViewLocationFormatAttribute([NotNull] string format)
+ {
+ Format = format;
+ }
+
+ [NotNull] public string Format { get; }
+ }
+
+ [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
+ public sealed class AspMvcMasterLocationFormatAttribute : Attribute
+ {
+ public AspMvcMasterLocationFormatAttribute([NotNull] string format)
+ {
+ Format = format;
+ }
+
+ [NotNull] public string Format { get; }
+ }
+
+ [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
+ public sealed class AspMvcPartialViewLocationFormatAttribute : Attribute
+ {
+ public AspMvcPartialViewLocationFormatAttribute([NotNull] string format)
+ {
+ Format = format;
+ }
+
+ [NotNull] public string Format { get; }
+ }
+
+ [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
+ public sealed class AspMvcViewLocationFormatAttribute : Attribute
+ {
+ public AspMvcViewLocationFormatAttribute([NotNull] string format)
+ {
+ Format = format;
+ }
+
+ [NotNull] public string Format { get; }
+ }
+
+ ///
+ /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter
+ /// is an MVC action. If applied to a method, the MVC action name is calculated
+ /// implicitly from the context. Use this attribute for custom wrappers similar to
+ /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String).
+ ///
+ [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
+ public sealed class AspMvcActionAttribute : Attribute
+ {
+ public AspMvcActionAttribute() { }
+
+ public AspMvcActionAttribute([NotNull] string anonymousProperty)
+ {
+ AnonymousProperty = anonymousProperty;
+ }
+
+ [CanBeNull] public string AnonymousProperty { get; }
+ }
+
+ ///
+ /// ASP.NET MVC attribute. Indicates that the marked parameter is an MVC area.
+ /// Use this attribute for custom wrappers similar to
+ /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String).
+ ///
+ [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)]
+ public sealed class AspMvcAreaAttribute : Attribute
+ {
+ public AspMvcAreaAttribute() { }
+
+ public AspMvcAreaAttribute([NotNull] string anonymousProperty)
+ {
+ AnonymousProperty = anonymousProperty;
+ }
+
+ [CanBeNull] public string AnonymousProperty { get; }
+ }
+
+ ///
+ /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is
+ /// an MVC controller. If applied to a method, the MVC controller name is calculated
+ /// implicitly from the context. Use this attribute for custom wrappers similar to
+ /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String, String).
+ ///
+ [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
+ public sealed class AspMvcControllerAttribute : Attribute
+ {
+ public AspMvcControllerAttribute() { }
+
+ public AspMvcControllerAttribute([NotNull] string anonymousProperty)
+ {
+ AnonymousProperty = anonymousProperty;
+ }
+
+ [CanBeNull] public string AnonymousProperty { get; }
+ }
+
+ ///
+ /// ASP.NET MVC attribute. Indicates that the marked parameter is an MVC Master. Use this attribute
+ /// for custom wrappers similar to System.Web.Mvc.Controller.View(String, String).
+ ///
+ [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)]
+ public sealed class AspMvcMasterAttribute : Attribute { }
+
+ ///
+ /// ASP.NET MVC attribute. Indicates that the marked parameter is an MVC model type. Use this attribute
+ /// for custom wrappers similar to System.Web.Mvc.Controller.View(String, Object).
+ ///
+ [AttributeUsage(AttributeTargets.Parameter)]
+ public sealed class AspMvcModelTypeAttribute : Attribute { }
+
+ ///
+ /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is an MVC
+ /// partial view. If applied to a method, the MVC partial view name is calculated implicitly
+ /// from the context. Use this attribute for custom wrappers similar to
+ /// System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial(HtmlHelper, String).
+ ///
+ [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
+ public sealed class AspMvcPartialViewAttribute : Attribute { }
+
+ ///
+ /// ASP.NET MVC attribute. Allows disabling inspections for MVC views within a class or a method.
+ ///
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
+ public sealed class AspMvcSuppressViewErrorAttribute : Attribute { }
+
+ ///
+ /// ASP.NET MVC attribute. Indicates that a parameter is an MVC display template.
+ /// Use this attribute for custom wrappers similar to
+ /// System.Web.Mvc.Html.DisplayExtensions.DisplayForModel(HtmlHelper, String).
+ ///
+ [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)]
+ public sealed class AspMvcDisplayTemplateAttribute : Attribute { }
+
+ ///
+ /// ASP.NET MVC attribute. Indicates that the marked parameter is an MVC editor template.
+ /// Use this attribute for custom wrappers similar to
+ /// System.Web.Mvc.Html.EditorExtensions.EditorForModel(HtmlHelper, String).
+ ///
+ [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)]
+ public sealed class AspMvcEditorTemplateAttribute : Attribute { }
+
+ ///
+ /// ASP.NET MVC attribute. Indicates that the marked parameter is an MVC template.
+ /// Use this attribute for custom wrappers similar to
+ /// System.ComponentModel.DataAnnotations.UIHintAttribute(System.String).
+ ///
+ [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)]
+ public sealed class AspMvcTemplateAttribute : Attribute { }
+
+ ///
+ /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter
+ /// is an MVC view component. If applied to a method, the MVC view name is calculated implicitly
+ /// from the context. Use this attribute for custom wrappers similar to
+ /// System.Web.Mvc.Controller.View(Object).
+ ///
+ [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
+ public sealed class AspMvcViewAttribute : Attribute { }
+
+ ///
+ /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter
+ /// is an MVC view component name.
+ ///
+ [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)]
+ public sealed class AspMvcViewComponentAttribute : Attribute { }
+
+ ///
+ /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter
+ /// is an MVC view component view. If applied to a method, the MVC view component view name is default.
+ ///
+ [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
+ public sealed class AspMvcViewComponentViewAttribute : Attribute { }
+
+ ///
+ /// ASP.NET MVC attribute. When applied to a parameter of an attribute,
+ /// indicates that this parameter is an MVC action name.
+ ///
+ ///
+ /// [ActionName("Foo")]
+ /// public ActionResult Login(string returnUrl) {
+ /// ViewBag.ReturnUrl = Url.Action("Foo"); // OK
+ /// return RedirectToAction("Bar"); // Error: Cannot resolve action
+ /// }
+ ///
+ [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)]
+ public sealed class AspMvcActionSelectorAttribute : Attribute { }
+
+ #endregion
+
+ #region ASP.NET Routing
+
+ ///
+ /// Indicates that the marked parameter, field, or property is a route template.
+ ///
+ ///
+ /// This attribute allows IDE to recognize the use of web frameworks' route templates
+ /// to enable syntax highlighting, code completion, navigation, rename and other features in string literals.
+ ///
+ [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)]
+ public sealed class RouteTemplateAttribute : Attribute { }
+
+ ///
+ /// Indicates that the marked type is custom route parameter constraint,
+ /// which is registered in application's Startup with name ConstraintName
+ ///
+ ///
+ /// You can specify ProposedType if target constraint matches only route parameters of specific type,
+ /// it will allow IDE to create method's parameter from usage in route template
+ /// with specified type instead of default System.String
+ /// and check if constraint's proposed type conflicts with matched parameter's type
+ ///
+ [AttributeUsage(AttributeTargets.Class)]
+ public sealed class RouteParameterConstraintAttribute : Attribute
+ {
+ [NotNull] public string ConstraintName { get; }
+ [CanBeNull] public Type ProposedType { get; set; }
+
+ public RouteParameterConstraintAttribute([NotNull] string constraintName)
+ {
+ ConstraintName = constraintName;
+ }
+ }
+
+ ///
+ /// Indicates that the marked parameter, field, or property is an URI string.
+ ///
+ ///
+ /// This attribute enables code completion, navigation, rename and other features
+ /// in URI string literals assigned to annotated parameter, field or property.
+ ///
+ [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)]
+ public sealed class UriStringAttribute : Attribute
+ {
+ public UriStringAttribute() { }
+
+ public UriStringAttribute(string httpVerb)
+ {
+ HttpVerb = httpVerb;
+ }
+
+ [CanBeNull] public string HttpVerb { get; }
+ }
+
+ ///
+ /// Indicates that the marked method declares routing convention for ASP.NET
+ ///
+ ///
+ /// IDE will analyze all usages of methods marked with this attribute,
+ /// and will add all routes to completion, navigation and other features over URI strings
+ ///
+ [AttributeUsage(AttributeTargets.Method)]
+ public sealed class AspRouteConventionAttribute : Attribute
+ {
+ public AspRouteConventionAttribute() { }
+
+ public AspRouteConventionAttribute(string predefinedPattern)
+ {
+ PredefinedPattern = predefinedPattern;
+ }
+
+ [CanBeNull] public string PredefinedPattern { get; }
+ }
+
+ ///
+ /// Indicates that the marked method parameter contains default route values of routing convention for ASP.NET
+ ///
+ [AttributeUsage(AttributeTargets.Parameter)]
+ public sealed class AspDefaultRouteValuesAttribute : Attribute { }
+
+ ///
+ /// Indicates that the marked method parameter contains constraints on route values of routing convention for ASP.NET
+ ///
+ [AttributeUsage(AttributeTargets.Parameter)]
+ public sealed class AspRouteValuesConstraintsAttribute : Attribute { }
+
+ ///
+ /// Indicates that the marked parameter or property contains routing order provided by ASP.NET routing attribute
+ ///
+ [AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter)]
+ public sealed class AspRouteOrderAttribute : Attribute { }
+
+ ///
+ /// Indicates that the marked parameter or property contains HTTP verbs provided by ASP.NET routing attribute
+ ///
+ [AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter)]
+ public sealed class AspRouteVerbsAttribute : Attribute { }
+
+ ///
+ /// Indicates that the marked attribute is used for attribute routing in ASP.NET
+ ///
+ ///
+ /// IDE will analyze all usages of attributes marked with this attribute,
+ /// and will add all routes to completion, navigation and other features over URI strings
+ ///
+ [AttributeUsage(AttributeTargets.Class)]
+ public sealed class AspAttributeRoutingAttribute : Attribute
+ {
+ public string HttpVerb { get; set; }
+ }
+
+ ///
+ /// Indicates that the marked method declares ASP.NET Minimal API endpoint
+ ///
+ ///
+ /// IDE will analyze all usages of methods marked with this attribute,
+ /// and will add all routes to completion, navigation and other features over URI strings
+ ///
+ [AttributeUsage(AttributeTargets.Method)]
+ public sealed class AspMinimalApiDeclarationAttribute : Attribute
+ {
+ public string HttpVerb { get; set; }
+ }
+
+ ///
+ /// Indicates that the marked parameter contains ASP.NET Minimal API endpoint handler
+ ///
+ [AttributeUsage(AttributeTargets.Parameter)]
+ public sealed class AspMinimalApiHandlerAttribute : Attribute { }
+
+ #endregion
+
+ #region Razor
+
+ [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field)]
+ public sealed class HtmlElementAttributesAttribute : Attribute
+ {
+ public HtmlElementAttributesAttribute() { }
+
+ public HtmlElementAttributesAttribute([NotNull] string name)
+ {
+ Name = name;
+ }
+
+ [CanBeNull] public string Name { get; }
+ }
+
+ [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)]
+ public sealed class HtmlAttributeValueAttribute : Attribute
+ {
+ public HtmlAttributeValueAttribute([NotNull] string name)
+ {
+ Name = name;
+ }
+
+ [NotNull] public string Name { get; }
+ }
+
+ ///
+ /// Razor attribute. Indicates that the marked parameter or method is a Razor section.
+ /// Use this attribute for custom wrappers similar to
+ /// System.Web.WebPages.WebPageBase.RenderSection(String).
+ ///
+ [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)]
+ public sealed class RazorSectionAttribute : Attribute { }
+
+ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
+ public sealed class RazorImportNamespaceAttribute : Attribute
+ {
+ public RazorImportNamespaceAttribute([NotNull] string name)
+ {
+ Name = name;
+ }
+
+ [NotNull] public string Name { get; }
+ }
+
+ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
+ public sealed class RazorInjectionAttribute : Attribute
+ {
+ public RazorInjectionAttribute([NotNull] string type, [NotNull] string fieldName)
+ {
+ Type = type;
+ FieldName = fieldName;
+ }
+
+ [NotNull] public string Type { get; }
+
+ [NotNull] public string FieldName { get; }
+ }
+
+ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
+ public sealed class RazorDirectiveAttribute : Attribute
+ {
+ public RazorDirectiveAttribute([NotNull] string directive)
+ {
+ Directive = directive;
+ }
+
+ [NotNull] public string Directive { get; }
+ }
+
+ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
+ public sealed class RazorPageBaseTypeAttribute : Attribute
+ {
+ public RazorPageBaseTypeAttribute([NotNull] string baseType)
+ {
+ BaseType = baseType;
+ }
+ public RazorPageBaseTypeAttribute([NotNull] string baseType, string pageName)
+ {
+ BaseType = baseType;
+ PageName = pageName;
+ }
+
+ [NotNull] public string BaseType { get; }
+ [CanBeNull] public string PageName { get; }
+ }
+
+ [AttributeUsage(AttributeTargets.Method)]
+ public sealed class RazorHelperCommonAttribute : Attribute { }
+
+ [AttributeUsage(AttributeTargets.Property)]
+ public sealed class RazorLayoutAttribute : Attribute { }
+
+ [AttributeUsage(AttributeTargets.Method)]
+ public sealed class RazorWriteLiteralMethodAttribute : Attribute { }
+
+ [AttributeUsage(AttributeTargets.Method)]
+ public sealed class RazorWriteMethodAttribute : Attribute { }
+
+ [AttributeUsage(AttributeTargets.Parameter)]
+ public sealed class RazorWriteMethodParameterAttribute : Attribute { }
+
+ #endregion
+
+ #region XAML
+
+ ///
+ /// XAML attribute. Indicates the type that has ItemsSource property and should be treated
+ /// as ItemsControl-derived type, to enable inner items DataContext type resolve.
+ ///
+ [AttributeUsage(AttributeTargets.Class)]
+ public sealed class XamlItemsControlAttribute : Attribute { }
+
+ ///
+ /// XAML attribute. Indicates the property of some BindingBase-derived type, that
+ /// is used to bind some item of ItemsControl-derived type. This annotation will
+ /// enable the DataContext type resolve for XAML bindings for such properties.
+ ///
+ ///
+ /// Property should have the tree ancestor of the ItemsControl type or
+ /// marked with the attribute.
+ ///
+ [AttributeUsage(AttributeTargets.Property)]
+ public sealed class XamlItemBindingOfItemsControlAttribute : Attribute { }
+
+ ///
+ /// XAML attribute. Indicates the property of some Style-derived type, that
+ /// is used to style items of ItemsControl-derived type. This annotation will
+ /// enable the DataContext type resolve for XAML bindings for such properties.
+ ///
+ ///
+ /// Property should have the tree ancestor of the ItemsControl type or
+ /// marked with the attribute.
+ ///
+ [AttributeUsage(AttributeTargets.Property)]
+ public sealed class XamlItemStyleOfItemsControlAttribute : Attribute { }
+
+ ///
+ /// XAML attribute. Indicates that DependencyProperty has OneWay binding mode by default.
+ ///
+ ///
+ /// This attribute must be applied to DependencyProperty's CLR accessor property if it is present, to DependencyProperty descriptor field otherwise.
+ ///
+ [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
+ public sealed class XamlOneWayBindingModeByDefaultAttribute : Attribute { }
+
+ ///
+ /// XAML attribute. Indicates that DependencyProperty has TwoWay binding mode by default.
+ ///
+ ///
+ /// This attribute must be applied to DependencyProperty's CLR accessor property if it is present, to DependencyProperty descriptor field otherwise.
+ ///
+ [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
+ public sealed class XamlTwoWayBindingModeByDefaultAttribute : Attribute { }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/Hawkeye.VisionBuilder.Workflow/BaseOperation.cs b/Hawkeye.VisionBuilder.Workflow/BaseOperation.cs
new file mode 100644
index 0000000..f539355
--- /dev/null
+++ b/Hawkeye.VisionBuilder.Workflow/BaseOperation.cs
@@ -0,0 +1,284 @@
+using Hawkeye.VisionBuilder.Workflow.Datatypes;
+using Hawkeye.VisionBuilder.Workflow.Links;
+using Hawkeye.VisionBuilder.Workflow.Operations.Attributes;
+using Microsoft.Scripting.Hosting;
+using Ninject.Infrastructure.Language;
+using OpenCvSharp;
+using System.Diagnostics;
+using System.Reflection;
+using System.Runtime;
+using System.Runtime.CompilerServices;
+using System.Text.RegularExpressions;
+using System.Xml;
+using System.Xml.Linq;
+using RectangleElement = Hawkeye.VisionBuilder.Workflow.Datatypes.Elements.Rectangle.RectangleElement;
+
+namespace Hawkeye.VisionBuilder.Workflow
+{
+ public abstract class BaseOperation
+ {
+ private static readonly ScriptScope _scope;
+ private static readonly ScriptEngine _engine;
+
+ static BaseOperation()
+ {
+ _engine = IronPython.Hosting.Python.CreateEngine();
+ _scope = _engine.CreateScope();
+ }
+ protected BaseOperation()
+ {
+ TypeName = MakeOperationName(this.GetType().Name);
+ Label = MakeOperationName(this.GetType().Name,false);
+
+
+ }
+
+ public static void SetScriptValue(string name, T value)
+ {
+ _scope.SetVariable(name,value);
+ }
+
+ public static T GetScriptValue(ScriptValue scriptValue)
+ {
+ return _engine.Execute(scriptValue.Script,_scope);
+ }
+ [NotForTool]
+ public bool CanHaveProcessingError { get; set; } = true;
+ [NotForTool]
+ public string Status { get; set; }
+
+ protected bool CheckImageExists(Context context)
+ {
+ if (context.ActiveImage == null)
+ {
+ Status = "No image available";
+ Result = false;
+ return false;
+ }
+
+ return true;
+ }
+
+ protected bool CheckColorful(Context context)
+ {
+ if (context.ActiveImage.ImageData.Type() != MatType.CV_8UC3)
+ {
+ Status="Image is not colorful";
+ Result = false;
+ return false;
+ }
+
+ return true;
+ }
+
+ protected bool CheckGrayscale(Context context)
+ {
+ if (context.ActiveImage.ImageData.Type() != MatType.CV_8UC1)
+ {
+ Status="Image is not grayscale";
+ Result = false;
+ return false;
+ }
+
+ return true;
+ }
+ [NotForTool]
+ public bool NeedsUpdate { get; set; } = true;
+ [NotForTool]
+ public Guid Id { get; protected set; }= Guid.NewGuid();
+ public static string MakeOperationName(string typeName,bool useSpaces=true)
+ {
+ return Regex.Replace(typeName.Replace("Operation", ""), "(\\B[A-Z]+?(?=[A-Z][^A-Z])|\\B[A-Z]+?(?=[^A-Z]))", (useSpaces?" ":"")+"$1");
+ }
+
+ public bool Result { get; set; } = true;
+ public string ResultString
+ {
+ get
+ {
+ return this.ToString();
+ }
+ }
+ [NotForTool]
+ public string TypeName { get; private set; }
+ [NotForTool]
+ public string Label { get; set; }
+
+ public TimeSpan ExecutionTime { get; set; }
+ public string ExecutionTimeString
+ {
+ get
+ {
+ return ((int)ExecutionTime.TotalMilliseconds) + " ms";
+ }
+ }
+
+ public void Interpret(Context context)
+ {
+
+
+ Result = true;
+ Status = "";
+ Stopwatch sw = Stopwatch.StartNew();
+ InterpretInternal(context);
+ sw.Stop();
+ ExecutionTime = sw.Elapsed;
+ _scope.SetVariable(Label,this);
+
+ }
+
+ protected abstract void InterpretInternal(Context context);
+
+ public Dictionary GetParameters()
+ {
+
+ var props = this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
+ .Where(x=>!x.HasAttribute(typeof(NotForToolAttribute)));
+ return props.ToDictionary(x => x.Name, x => x.GetValue(this)!);
+ }
+
+ public virtual void SetParameters(Dictionary parameters)
+ {
+ foreach (KeyValuePair pair in parameters)
+ {
+ this.GetType().GetProperty(pair.Key,
+ BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
+ ?.SetValue(this,pair.Value);
+ }
+ }
+
+
+ public virtual void SaveXML(XmlWriter writer)
+ {
+ writer.WriteAttributeString("Id", Id.ToString());
+ writer.WriteAttributeString("Label", Label);
+
+ // Save all public properties that are not marked with NotForTool
+ var properties = GetParameters();
+ foreach (var property in properties)
+ {
+ if (property.Value != null)
+ {
+ writer.WriteStartElement("Property");
+ writer.WriteAttributeString("Name", property.Key);
+ writer.WriteAttributeString("Type", property.Value.GetType().FullName);
+
+ // Handle different types appropriately
+ if (property.Value is string || property.Value.GetType().IsPrimitive || property.Value.GetType().IsEnum)
+ {
+ writer.WriteString(property.Value.ToString());
+ }
+ else if (property.Value is Guid guid)
+ {
+ writer.WriteString(guid.ToString());
+ }
+ else
+ {
+ // For complex types, convert to string representation
+ writer.WriteString(property.Value.ToString());
+ }
+
+ writer.WriteEndElement();
+ }
+ }
+ }
+
+ public virtual void LoadXML(XmlReader reader)
+ {
+ if (reader.GetAttribute("Id") != null)
+ Id = new Guid(reader.GetAttribute("Id"));
+ if (reader.GetAttribute("Label") != null)
+ Label = reader.GetAttribute("Label");
+
+ var properties = new Dictionary();
+
+ while (reader.Read())
+ {
+ if (reader.NodeType == XmlNodeType.Element && reader.Name == "Property")
+ {
+ string propertyName = reader.GetAttribute("Name");
+ string propertyType = reader.GetAttribute("Type");
+ string propertyValue = reader.ReadElementContentAsString();
+
+ if (!string.IsNullOrEmpty(propertyName) && !string.IsNullOrEmpty(propertyValue))
+ {
+ // Convert string value back to appropriate type
+ object convertedValue = ConvertFromString(propertyValue, propertyType);
+ if (convertedValue != null)
+ {
+ properties[propertyName] = convertedValue;
+ }
+ }
+ }
+ else if (reader.NodeType == XmlNodeType.EndElement && reader.Name == GetOperationElementName())
+ {
+ break;
+ }
+ }
+
+ if (properties.Count > 0)
+ {
+ SetParameters(properties);
+ }
+ }
+
+ private string GetOperationElementName()
+ {
+ return this.GetType().Name;
+ }
+
+ private object ConvertFromString(string value, string typeName)
+ {
+ try
+ {
+ var type = Type.GetType(typeName);
+ if (type == null) return value;
+
+ if (type == typeof(string))
+ return value;
+ else if (type == typeof(int))
+ return int.Parse(value);
+ else if (type == typeof(double))
+ return double.Parse(value);
+ else if (type == typeof(float))
+ return float.Parse(value);
+ else if (type == typeof(bool))
+ return bool.Parse(value);
+ else if (type == typeof(Guid))
+ return new Guid(value);
+ else if (type.IsEnum)
+ return Enum.Parse(type, value);
+ else
+ return value; // Fallback to string
+ }
+ catch
+ {
+ return value; // Fallback to string if conversion fails
+ }
+ }
+
+
+ public override string ToString()
+ {
+ return Status;
+ }
+
+ public virtual void Save(BinaryWriter bw)
+ {
+ bw.Write(Id.ToString());
+ bw.Write(Label);
+ }
+
+ public virtual void Load(BinaryReader br)
+ {
+ Id = new Guid(br.ReadString());
+ Label = br.ReadString();
+ }
+
+ public void SetError(string eMessage)
+ {
+ Status=eMessage;
+ Result = false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Hawkeye.VisionBuilder.Workflow/BaseOperationDecorator.cs b/Hawkeye.VisionBuilder.Workflow/BaseOperationDecorator.cs
new file mode 100644
index 0000000..bfbe1fa
--- /dev/null
+++ b/Hawkeye.VisionBuilder.Workflow/BaseOperationDecorator.cs
@@ -0,0 +1,24 @@
+namespace Hawkeye.VisionBuilder.Workflow;
+
+public class BaseOperationDecorator
+{
+ private readonly BaseOperation _baseOperation;
+
+ public BaseOperationDecorator(BaseOperation baseOperation)
+ {
+ _baseOperation = baseOperation;
+ }
+
+ public bool Result => Operation.Result;
+ public string ResultString => Operation.ToString();
+ public string TypeName => Operation.TypeName;
+ public string Label
+ {
+ get => Operation.Label;
+ set => Operation.Label = value;
+ }
+
+ public string ExecutionTimeString => Operation.ExecutionTimeString;
+
+ public BaseOperation Operation => _baseOperation;
+}
\ No newline at end of file
diff --git a/Hawkeye.VisionBuilder.Workflow/CLAUDE.md b/Hawkeye.VisionBuilder.Workflow/CLAUDE.md
new file mode 100644
index 0000000..f6934f3
--- /dev/null
+++ b/Hawkeye.VisionBuilder.Workflow/CLAUDE.md
@@ -0,0 +1,157 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Project Overview
+
+Hawkeye.VisionBuilder.Workflow is a computer vision workflow system built on .NET 8.0 that processes images through configurable operation pipelines. The system supports both CPU and GPU configurations with ONNX runtime for AI models.
+
+## Build Configuration
+
+The project uses conditional package references based on configuration:
+- **CPU Configuration**: Uses `YoloV8` (4.1.5) and `Microsoft.ML.OnnxRuntime` (1.18.0)
+- **GPU Configuration**: Uses `YoloV8.Gpu` (4.1.7) and `Microsoft.ML.OnnxRuntime.Gpu` (1.18.0)
+
+Build the project with:
+```bash
+dotnet build -c Debug # For GPU configuration
+dotnet build -c CPU # For CPU-only configuration
+```
+
+## Core Architecture
+
+### Operation System
+- **BaseOperation**: Abstract base class for all image processing operations. All operations inherit from this and implement `InterpretInternal(Context context)`
+- **Context**: Holds the current processing state including `ActiveImage`, `LastCameraImage`, `GraphicsElements`, and image `Memory`
+- **OperationDiscoveryService**: Dynamically discovers and instantiates operations using reflection and Ninject DI
+
+### Operation Categories
+Operations are organized in folders by category:
+- `AI/`: AI model operations (YOLO, ONNX models, color classification)
+- `Simple/`: Basic operations (blob detection, edge finding)
+- `Filters/`: Image filtering operations
+- `Morphology/`: Morphological operations (dilation, erosion, etc.)
+- `Image/`: Image manipulation (convert, channels, etc.)
+- `Basic/`: Fundamental operations (threshold, blur, etc.)
+
+### Key Components
+- **HawkeyeImage**: Wrapper around OpenCV Mat with filename metadata
+- **WorkflowConfiguration**: Manages camera types, Python paths, outputs, and recipe selection
+- **IImageSource**: Interface for image input sources with event-driven architecture
+- **Graphics Elements**: Drawable elements (rectangles, lines, polygons) for ROI definition
+
+### Python Integration
+- Uses IronPython for scripting capabilities
+- Python models executed via `PythonModelProxy` and `PythonModelProxyRPC`
+- Default Python path: `~/miniconda3/envs/ai3_cpu/python.exe`
+
+### Operation Attributes
+- `[Category("name")]`: Groups operations in UI
+- `[NotForTool]`: Excludes properties from parameter serialization
+- `[IgnoreOperation]`: Excludes operations from discovery
+
+## Development Patterns
+
+### Creating New Operations
+1. Inherit from `BaseOperation`
+2. Add `[Category("CategoryName")]` attribute
+3. Implement `InterpretInternal(Context context)` method
+4. Use helper methods: `CheckImageExists()`, `CheckColorful()`, `CheckGrayscale()`
+5. Set `Result` and `Status` properties for operation feedback
+
+### Image Processing Flow
+1. Operations receive `Context` with current `ActiveImage`
+2. Process image using OpenCV operations
+3. Update context with results (new images, graphics elements)
+4. Set operation status and result state
+
+### Memory Management
+Operations can store/retrieve images using `context.Memory` dictionary for intermediate results.
+
+## Recipe Serialization System
+
+The workflow system supports two serialization formats for saving and loading recipes:
+
+### Binary Serialization (.hrcp files)
+The original format using `BinaryWriter` and `BinaryReader`:
+- **WorkflowList.Save(BinaryWriter)**: Saves configuration, operations, and recipe image
+- **WorkflowList.Load(BinaryReader, OperationDiscoveryService)**: Loads complete workflow
+- **BaseOperation.Save(BinaryWriter)**: Saves operation ID and label
+- **BaseOperation.Load(BinaryReader)**: Loads operation ID and label
+
+### XML Serialization (.xhrcp files)
+The new human-readable format using `XmlWriter` and `XmlReader`:
+- **WorkflowList.SaveXML(XmlWriter)**: Saves complete workflow as structured XML
+- **WorkflowList.LoadXML(XmlReader, OperationDiscoveryService)**: Loads workflow from XML
+- **BaseOperation.SaveXML(XmlWriter)**: Saves operation properties and metadata
+- **BaseOperation.LoadXML(XmlReader)**: Loads operation properties with type conversion
+
+### XML Structure
+```xml
+
+
+
+
+ Value
+ ...
+
+
+ Base64EncodedImageData
+
+```
+
+### Implementing Custom Operation Serialization
+
+For operations that need custom XML serialization beyond the default property serialization:
+
+#### Override SaveXML for Custom Save Logic:
+```csharp
+public override void SaveXML(XmlWriter writer)
+{
+ base.SaveXML(writer); // Save standard properties
+
+ // Custom serialization logic
+ writer.WriteStartElement("CustomData");
+ writer.WriteAttributeString("SpecialProperty", SpecialValue.ToString());
+ writer.WriteEndElement();
+}
+```
+
+#### Override LoadXML for Custom Load Logic:
+```csharp
+public override void LoadXML(XmlReader reader)
+{
+ base.LoadXML(reader); // Load standard properties
+
+ // Custom deserialization logic
+ while (reader.Read())
+ {
+ if (reader.NodeType == XmlNodeType.Element && reader.Name == "CustomData")
+ {
+ SpecialValue = reader.GetAttribute("SpecialProperty");
+ }
+ else if (reader.NodeType == XmlNodeType.EndElement && reader.Name == GetOperationElementName())
+ {
+ break;
+ }
+ }
+}
+```
+
+#### Override GetOperationElementName for Custom Element Names:
+```csharp
+protected override string GetOperationElementName()
+{
+ return "MyCustomOperation"; // Used for XML end element detection
+}
+```
+
+### Supported Property Types in XML Serialization
+The default XML serialization handles these types automatically:
+- Primitive types (int, double, float, bool)
+- String
+- Guid
+- Enums
+- Complex types (converted to string representation)
+
+For complex types requiring special serialization, override the SaveXML/LoadXML methods.
\ No newline at end of file
diff --git a/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/CategoryItem.cs b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/CategoryItem.cs
new file mode 100644
index 0000000..b09ecd2
--- /dev/null
+++ b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/CategoryItem.cs
@@ -0,0 +1,44 @@
+using Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
+
+namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses;
+
+public class CategoryItem:IFilterParent
+{
+ public string Name { get; set; }
+ public List Filters { get; set; } = new List();
+
+ public void Save(BinaryWriter writer)
+ {
+ writer.Write(Name);
+ writer.Write(Filters.Count);
+ foreach (var filter in Filters)
+ {
+ var type = filter.GetType();
+ writer.Write(type.FullName);
+ filter.Save(writer);
+ }
+ }
+
+ public static CategoryItem Load(BinaryReader reader)
+ {
+ var category = new CategoryItem();
+ category.Name = reader.ReadString();
+ var count = reader.ReadInt32();
+ for (int i = 0; i < count; i++)
+ {
+ var typeName = reader.ReadString();
+ var type = Type.GetType(typeName);
+ var filter = (IFilter)Activator.CreateInstance(type);
+ filter.Load(reader);
+ filter.Parent = category;
+ category.Filters.Add(filter);
+ }
+ return category;
+ }
+
+ public override string ToString()
+ {
+ return Name;
+ }
+}
+
diff --git a/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/CategoryTree.cs b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/CategoryTree.cs
new file mode 100644
index 0000000..60ec456
--- /dev/null
+++ b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/CategoryTree.cs
@@ -0,0 +1,33 @@
+namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses;
+
+public class CategoryTree
+{
+ public List CategoryItems { get; set; }=new List();
+
+ public void Save(BinaryWriter writer)
+ {
+ writer.Write(CategoryItems.Count);
+ foreach (var category in CategoryItems)
+ {
+ category.Save(writer);
+ }
+ }
+
+ public static CategoryTree Load(BinaryReader reader)
+ {
+ var tree = new CategoryTree();
+ var count = reader.ReadInt32();
+ for (int i = 0; i < count; i++)
+ {
+ tree.CategoryItems.Add(CategoryItem.Load(reader));
+ }
+ return tree;
+ }
+
+ public static CategoryTree LoadFromFile(string path)
+ {
+ using var file = File.OpenRead(path);
+ using var reader = new BinaryReader(file);
+ return Load(reader);
+ }
+}
\ No newline at end of file
diff --git a/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/ColorConfig.cs b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/ColorConfig.cs
new file mode 100644
index 0000000..b901d5f
--- /dev/null
+++ b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/ColorConfig.cs
@@ -0,0 +1,47 @@
+namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses
+{
+ public class ColorConfig
+ {
+ private int _intensityMaxGrey;
+
+ public ColorConfig()
+ {
+ MaxBlobSize = 2000;
+ HueHighSaturationMinGrey = 20;
+ HueHighSaturationMaxGrey = 50;
+ HighSaturationMinGrey = 100;
+ HighSaturationMaxGrey = 255;
+ ClosingCircleRadius = 8.0;
+ IntensityMaxGrey = 255;
+ IntensityMinGrey = 0;
+ MedianRadius = 10;
+ MedianMargin = 1;
+ UseMedianFilter = true;
+ }
+
+ public int MaxBlobSize { get; set; }
+ public int HueHighSaturationMinGrey { get; set; }
+ public int HueHighSaturationMaxGrey { get; set; }
+ public int HighSaturationMinGrey { get; set; }
+ public int HighSaturationMaxGrey { get; set; }
+
+ public int IntensityMaxGrey
+ {
+ get { return _intensityMaxGrey; }
+ set { _intensityMaxGrey = value; }
+ }
+
+ public int IntensityMinGrey { get; set; }
+
+ public double ClosingCircleRadius { get; set; }
+
+ public int MedianRadius { get; set; }
+ public int MedianMargin { get; set; }
+
+ public bool UseMedianFilter { get; set; }
+
+ public bool GroupColorEnabled { get; set; }
+ public List GroupColorValues { get; set; }=new List();
+ public HSIValue HSI { get; set; }
+ }
+}
diff --git a/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/AddFilterItem.cs b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/AddFilterItem.cs
new file mode 100644
index 0000000..ebd3e9d
--- /dev/null
+++ b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/AddFilterItem.cs
@@ -0,0 +1,52 @@
+using OpenCvSharp;
+
+namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
+
+public class AddFilterItem : IFilter, IFilterParent
+{
+ public IFilterParent Parent { get; set; }
+
+ public List Filters { get; set; } = new List();
+
+ public void Transform(ref Mat image, ref Mat res)
+ {
+ using var tracker = new ResourcesTracker();
+ var temp = tracker.NewMat(res.Size(), MatType.CV_8UC1, Scalar.Black);
+ foreach (var filter in Filters)
+ {
+ filter.Transform(ref image, ref temp);
+ }
+ res = res + temp;
+ temp.Dispose();
+ }
+
+ public void Save(BinaryWriter bw)
+ {
+ bw.Write(Filters.Count);
+ foreach (var filter in Filters)
+ {
+ bw.Write(filter.GetType().FullName);
+ filter.Save(bw);
+ }
+ }
+
+ public void Load(BinaryReader reader)
+ {
+ int count = reader.ReadInt32();
+ for (int i = 0; i < count; i++)
+ {
+ var type = reader.ReadString();
+ var filter = (IFilter)Activator.CreateInstance(Type.GetType(type));
+ filter.Load(reader);
+ filter.Parent = this;
+ Filters.Add(filter);
+ }
+ }
+
+ public bool IsSelected { get; set; }
+
+ public override string ToString()
+ {
+ return "Add";
+ }
+}
\ No newline at end of file
diff --git a/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/BlobsFilterItem.cs b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/BlobsFilterItem.cs
new file mode 100644
index 0000000..ebb2a4e
--- /dev/null
+++ b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/BlobsFilterItem.cs
@@ -0,0 +1,66 @@
+using System.Text;
+using OpenCvSharp;
+
+namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
+
+public class BlobsFilterItem:IFilter
+{
+ public int MinArea { get; set; } = 0;
+ public int MaxArea { get; set; } = 999999;
+ public int MinRadius { get; set; } = 0;
+ public int MaxRadius { get; set; } = 999999;
+
+ public IFilterParent Parent { get; set; }
+ public void Transform(ref Mat image, ref Mat res)
+ {
+ var contours = res
+ .FindContoursAsArray(RetrievalModes.List, ContourApproximationModes.ApproxSimple);
+
+ Mat result = new Mat(res.Rows, res.Cols, MatType.CV_8U, new Scalar(0));
+
+ foreach (Point[] contour in contours)
+ {
+ var area = Cv2.ContourArea(contour);
+ if (area < MinArea || area > MaxArea)
+ continue;
+ var radius = GetContourRadius(contour);
+ if (radius < MinRadius || radius > MaxRadius)
+ continue;
+
+ Cv2.DrawContours(result, new[] { contour }, -1, new Scalar(255), -1);
+ }
+
+ res = result;
+ }
+ int GetContourRadius(Point[] contour)
+ {
+ Cv2.MinEnclosingCircle(contour, out var center, out var radius);
+ return (int)radius;
+ }
+ public void Save(BinaryWriter writer)
+ {
+ writer.Write(MinArea);
+ writer.Write(MaxArea);
+ writer.Write(MinRadius);
+ writer.Write(MaxRadius);
+ }
+
+ public void Load(BinaryReader reader)
+ {
+ MinArea = reader.ReadInt32();
+ MaxArea = reader.ReadInt32();
+ MinRadius = reader.ReadInt32();
+ MaxRadius = reader.ReadInt32();
+ }
+
+ public bool IsSelected { get; set; }
+
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.Append("Blobs: ");
+ sb.Append($"Area:{MinArea}-{MaxArea}, ");
+ sb.Append($"Radius:{MinRadius}-{MaxRadius}");
+ return sb.ToString();
+ }
+}
\ No newline at end of file
diff --git a/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/BlurFilterItem.cs b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/BlurFilterItem.cs
new file mode 100644
index 0000000..1b9f942
--- /dev/null
+++ b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/BlurFilterItem.cs
@@ -0,0 +1,30 @@
+using OpenCvSharp;
+
+namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
+
+public class BlurFilterItem:IFilter
+{
+ public IFilterParent Parent { get; set; }
+ public int Radius { get; set; } = 15;
+ public void Transform(ref Mat image, ref Mat res)
+ {
+ image = image.Blur(new Size(Radius, Radius));
+ }
+
+ public void Save(BinaryWriter writer)
+ {
+ writer.Write(Radius);
+ }
+
+ public void Load(BinaryReader reader)
+ {
+ Radius = reader.ReadInt32();
+ }
+
+ public bool IsSelected { get; set; }
+
+ public override string ToString()
+ {
+ return "Blur "+Radius;
+ }
+}
\ No newline at end of file
diff --git a/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/IFilter.cs b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/IFilter.cs
new file mode 100644
index 0000000..2a6678e
--- /dev/null
+++ b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/IFilter.cs
@@ -0,0 +1,13 @@
+using OpenCvSharp;
+
+namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
+
+public interface IFilter
+{
+ public IFilterParent Parent { get; set; }
+ void Transform(ref Mat image, ref Mat res);
+ void Save(BinaryWriter writer);
+ void Load(BinaryReader reader);
+ public bool IsSelected { get; set; }
+
+}
\ No newline at end of file
diff --git a/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/IFilterParent.cs b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/IFilterParent.cs
new file mode 100644
index 0000000..e696fea
--- /dev/null
+++ b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/IFilterParent.cs
@@ -0,0 +1,6 @@
+namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
+
+public interface IFilterParent
+{
+ public List Filters { get; set; }
+}
\ No newline at end of file
diff --git a/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/IntersectionFilterItem.cs b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/IntersectionFilterItem.cs
new file mode 100644
index 0000000..633c856
--- /dev/null
+++ b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/IntersectionFilterItem.cs
@@ -0,0 +1,52 @@
+using OpenCvSharp;
+
+namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
+
+public class IntersectionFilterItem : IFilter, IFilterParent
+{
+ public IFilterParent Parent { get; set; }
+
+ public List Filters { get; set; } = new List();
+
+ public void Transform(ref Mat image, ref Mat res)
+ {
+ using var tracker = new ResourcesTracker();
+ var temp = tracker.NewMat(res.Size(), MatType.CV_8UC1, Scalar.Black);
+ foreach (var filter in Filters)
+ {
+ filter.Transform(ref image, ref temp);
+ }
+ res = res & temp;
+ temp.Dispose();
+ }
+
+ public void Save(BinaryWriter bw)
+ {
+ bw.Write(Filters.Count);
+ foreach (var filter in Filters)
+ {
+ bw.Write(filter.GetType().FullName);
+ filter.Save(bw);
+ }
+ }
+
+ public void Load(BinaryReader reader)
+ {
+ int count = reader.ReadInt32();
+ for (int i = 0; i < count; i++)
+ {
+ var type = reader.ReadString();
+ var filter = (IFilter)Activator.CreateInstance(Type.GetType(type));
+ filter.Load(reader);
+ filter.Parent = this;
+ Filters.Add(filter);
+ }
+ }
+
+ public bool IsSelected { get; set; }
+
+ public override string ToString()
+ {
+ return "Intersection";
+ }
+}
\ No newline at end of file
diff --git a/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/MorphologyFilterItem.cs b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/MorphologyFilterItem.cs
new file mode 100644
index 0000000..c492152
--- /dev/null
+++ b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/MorphologyFilterItem.cs
@@ -0,0 +1,78 @@
+using System.Diagnostics;
+using OpenCvSharp;
+
+namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
+
+public class MorphologyFilterItem:IFilter
+{
+ public enum EMorphologyType
+ {
+ Erode,
+ Dilate,
+ Close,
+ Open
+ }
+
+ public int Size { get; set; } = 1;
+ public EMorphologyType MorphologyType { get; set; }=EMorphologyType.Erode;
+ public void Load(BinaryReader reader)
+ {
+ Size = reader.ReadInt32();
+ MorphologyType = (EMorphologyType)reader.ReadInt32();
+ }
+
+ public bool IsSelected { get; set; }
+
+ public IFilterParent Parent { get; set; }
+
+ public MorphologyFilterItem()
+ {
+ }
+
+ public MorphologyFilterItem(CategoryItem parent)
+ {
+ Parent = parent;
+ }
+
+ public void Transform(ref Mat image, ref Mat res)
+ {
+ if(Size==0)
+ return;
+
+ var sw=Stopwatch.StartNew();
+
+ var kernel = Cv2.GetStructuringElement(MorphShapes.Ellipse,new Size((int)(Size), (int)(Size)));
+
+ switch (MorphologyType)
+ {
+ case EMorphologyType.Dilate:
+ //Cv2.Dilate(res, res,kernel);
+ Cv2.MorphologyEx(res, res, MorphTypes.Dilate, kernel);
+ break;
+ case EMorphologyType.Erode:
+ Cv2.Erode(res, res,kernel);
+ break;
+ case EMorphologyType.Close:
+ Cv2.MorphologyEx(res, res, MorphTypes.Close, kernel);
+ break;
+ case EMorphologyType.Open:
+ Cv2.MorphologyEx(res, res, MorphTypes.Open, kernel);
+ break;
+ }
+
+ sw.Stop();
+
+
+ }
+
+ public void Save(BinaryWriter writer)
+ {
+ writer.Write(Size);
+ writer.Write((int)MorphologyType);
+ }
+
+ public override string ToString()
+ {
+ return $"{MorphologyType} {Size}";
+ }
+}
\ No newline at end of file
diff --git a/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/RangeFilterItem.cs b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/RangeFilterItem.cs
new file mode 100644
index 0000000..77abdb9
--- /dev/null
+++ b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/RangeFilterItem.cs
@@ -0,0 +1,101 @@
+using System.Windows.Markup;
+using OpenCvSharp;
+using Size = OpenCvSharp.Size;
+
+namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
+
+public class RangeFilterItem : IFilter
+{
+ public IFilterParent Parent { get; set; }
+
+ public RangeFilterItem(CategoryItem parent)
+ {
+ Parent = parent;
+ }
+
+ public RangeFilterItem()
+ {
+ }
+
+ public int HMin { get; set; } = 0;
+ public int HMax { get; set; } = 100;
+ public int SMin { get; set; } = 0;
+ public int SMax { get; set; } = 255;
+ public int VMin { get; set; } = 0;
+ public int VMax { get; set; } = 255;
+ public int Dilation { get; set; } = 0;
+ public int Closing { get; set; } = 0;
+
+
+ public override string ToString()
+ {
+ return $"H: {HMin}-{HMax}, S: {SMin}-{SMax}, V: {VMin}-{VMax}";
+ }
+
+ public void Transform(ref Mat hsv, ref Mat res)
+ {
+ var lower = new Scalar(HMin, SMin, VMin);
+ var upper = new Scalar(HMax, SMax, VMax);
+ var mask = hsv.InRange(lower, upper);
+
+ if (Dilation > 0)
+ {
+ // generate circular kernel
+ var kernel = Cv2.GetStructuringElement(MorphShapes.Ellipse, new Size(Dilation, Dilation));
+ mask = mask.Dilate(kernel);
+ kernel.Dispose();
+ }
+
+ if (Closing > 0)
+ {
+ // generate circular kernel
+ var kernel = Cv2.GetStructuringElement(MorphShapes.Ellipse, new Size(Closing, Closing));
+ mask = mask.MorphologyEx(MorphTypes.Close, kernel);
+ kernel.Dispose();
+ }
+
+ res+=mask;
+
+ mask.Dispose();
+ }
+
+ public void Save(BinaryWriter writer)
+ {
+ writer.Write(HMin);
+ writer.Write(HMax);
+ writer.Write(SMin);
+ writer.Write(SMax);
+ writer.Write(VMin);
+ writer.Write(VMax);
+ writer.Write(Dilation);
+ writer.Write(Closing);
+ }
+
+ public void Load(BinaryReader reader)
+ {
+ HMin = reader.ReadInt32();
+ HMax = reader.ReadInt32();
+ SMin = reader.ReadInt32();
+ SMax = reader.ReadInt32();
+ VMin = reader.ReadInt32();
+ VMax = reader.ReadInt32();
+ Dilation = reader.ReadInt32();
+ Closing = reader.ReadInt32();
+ }
+
+ public bool IsSelected { get; set; }
+
+ public static RangeFilterItem Load(BinaryReader reader, CategoryItem parent)
+ {
+ var filter = new RangeFilterItem(parent);
+ filter.HMin = reader.ReadInt32();
+ filter.HMax = reader.ReadInt32();
+ filter.SMin = reader.ReadInt32();
+ filter.SMax = reader.ReadInt32();
+ filter.VMin = reader.ReadInt32();
+ filter.VMax = reader.ReadInt32();
+ filter.Dilation = reader.ReadInt32();
+ filter.Closing = reader.ReadInt32();
+ return filter;
+ }
+}
\ No newline at end of file
diff --git a/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/SobelFilterItem.cs b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/SobelFilterItem.cs
new file mode 100644
index 0000000..88995d8
--- /dev/null
+++ b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/SobelFilterItem.cs
@@ -0,0 +1,25 @@
+using OpenCvSharp;
+
+namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
+
+public class SobelFilterItem:IFilter
+{
+ public IFilterParent Parent { get; set; }
+ public void Transform(ref Mat image, ref Mat res)
+ {
+ var filtered=image.Sobel(MatType.CV_32F, 1, 1, 3);
+ filtered.ConvertTo(image, MatType.CV_8UC3);
+ }
+
+ public void Save(BinaryWriter writer)
+ {
+
+ }
+
+ public void Load(BinaryReader reader)
+ {
+
+ }
+
+ public bool IsSelected { get; set; }
+}
\ No newline at end of file
diff --git a/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/SpatialFilterItem.cs b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/SpatialFilterItem.cs
new file mode 100644
index 0000000..36ff667
--- /dev/null
+++ b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/SpatialFilterItem.cs
@@ -0,0 +1,176 @@
+using System.Diagnostics;
+using OpenCvSharp;
+using System.Drawing;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography;
+using System.Threading.Tasks;
+using Hawkeye.VisionBuilder.Workflow.Datatypes;
+
+namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
+
+public class SpatialFilterItem : IFilter
+{
+ private Mat? _mask=null;
+
+ public IFilterParent Parent { get; set; }
+
+ public bool Subtract { get; set; }
+
+ public string? FilePath { get; set; }
+
+ public void ReloadMask()
+ {
+ if (!File.Exists(FilePath))
+ {
+ CreateMask();
+ }else
+ if (!string.IsNullOrEmpty(FilePath))
+ {
+ _mask = new Mat(FilePath,ImreadModes.Grayscale);
+ }
+ }
+
+ public SpatialFilterItem()
+ {
+
+ }
+
+ void CreateMask()
+ {
+ _mask = new Mat(13005 + 256, 256, MatType.CV_8UC1, (Scalar)0);
+ }
+
+ public SpatialFilterItem(CategoryItem parent) : this()
+ {
+ Parent = parent;
+ }
+
+ public Mat Mask
+ {
+ get
+ {
+ if (_mask == null)
+ {
+ if (FilePath != null)
+ {
+ ReloadMask();
+ }
+ else
+ {
+ CreateMask();
+ }
+ }
+
+ return _mask;
+ }
+ set
+ {
+ _mask = value;
+ }
+ }
+
+ struct PixelStruct
+ {
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 65536*3)]
+ public byte[] Pixels;
+ }
+ struct PixelStructMask
+ {
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3394816)]
+ public byte[] Pixels;
+ }
+ public static Mat lastInput;
+ public unsafe void Transform(ref Mat image, ref Mat res)
+ {
+
+ //var pixelStruct = Marshal.PtrToStructure(new IntPtr(image.DataPointer));
+ var candyBytes = GetPixels(image);
+ var candidates = new Mat(image.Size(), MatType.CV_8UC1, (Scalar)0);
+ Mat tmp=new Mat();
+ image.CopyTo(tmp);
+ lastInput = tmp;
+ //var pixelStructMask = Marshal.PtrToStructure(new IntPtr(Mask.DataPointer));
+
+ var maskBytes = GetPixels(Mask);
+
+ var len = candyBytes.Length;
+ var w = image.Width;
+ var mw = Mask.Width;
+
+ var lenDiv3 = len / 3;
+
+ for (var q = 0; q < lenDiv3; q++)
+ {
+ var i = q * 3;
+
+ var p = i / 3;
+ var y = p / w;
+ var x = p % w;
+
+ var yp = (int)candyBytes[i + 0];
+ var xp = candyBytes[i + 1];
+ var vp = ((byte) (candyBytes[i + 2] / 5f)) * 255;
+
+ var hp = yp + vp;
+
+ var maskColor = maskBytes[hp * mw + xp];
+ if (maskColor > 0)
+ candidates.Set(y, x, 255);
+ }
+
+
+
+
+ if (Subtract && !IsSelected)
+ {
+ res -= candidates;
+ }
+ else
+ {
+ res += candidates;
+
+ }
+
+
+
+ }
+
+ string Hash(Span bytes) {
+ using var sha = SHA256.Create();
+ var hash = sha.ComputeHash(bytes.ToArray());
+ return Convert.ToBase64String(hash);
+ }
+ unsafe Span GetPixels(Mat image) where T : unmanaged
+ {
+
+ return new Span(image.DataPointer, image.Width * image.Height * image.Channels());
+
+ }
+ public void Save(BinaryWriter writer)
+ {
+ writer.Write(FilePath??"");
+ writer.Write(Subtract);
+ }
+
+ public void Load(BinaryReader reader)
+ {
+ FilePath = reader.ReadString();
+ Console.WriteLine("Loaded path:"+FilePath);
+ if(FilePath.StartsWith("..\\../"))
+ FilePath = FilePath.Replace("..\\../", "..\\");
+ Subtract = reader.ReadBoolean();
+ }
+
+ public bool IsSelected { get; set; }
+
+
+ public override string ToString()
+ {
+ var totalColors = 0;
+ if (Mask != null)
+ {
+ totalColors = Mask.CountNonZero();
+ }
+ return $"{(Subtract ? "-" : "+")}Spatial Filter({totalColors} colors)";
+ }
+}
\ No newline at end of file
diff --git a/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/SpatialFilterItemGPU.cs b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/SpatialFilterItemGPU.cs
new file mode 100644
index 0000000..e2e229e
--- /dev/null
+++ b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/SpatialFilterItemGPU.cs
@@ -0,0 +1,177 @@
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using Hawkeye.VisionBuilder.Workflow.Datatypes;
+using ILGPU;
+using ILGPU.Runtime;
+using OpenCvSharp;
+
+namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
+
+[Obsolete]
+public class SpatialFilterItemGPU:IFilter
+{
+ private Mat? _mask = null;
+
+ private MemoryBuffer1D _maskGpu;
+
+
+
+ public IFilterParent Parent { get; set; }
+
+ public bool Subtract { get; set; }
+
+ public string? FilePath { get; set; }
+
+ public void ReloadMask()
+ {
+ if (!File.Exists(FilePath))
+ {
+ CreateMask();
+ }
+ else
+ if (!string.IsNullOrEmpty(FilePath))
+ {
+ _mask = new Mat(FilePath).CvtColor(ColorConversionCodes.BGR2GRAY);
+ }
+ LoadMaskToGpu();
+ }
+
+ private unsafe void LoadMaskToGpu()
+ {
+ var maskBytes = new Span(Mask.DataPointer, Mask.Width * Mask.Height).ToArray();
+ if (_maskGpu != null)
+ {
+ _maskGpu.Dispose();
+ }
+ _maskGpu = Kernels.Accelerator.Allocate1D(maskBytes);
+
+
+ }
+
+
+ private static MemoryBuffer1D? _imageMemory;
+ private static MemoryBuffer1D? _resMemory;
+
+
+ public SpatialFilterItemGPU()
+ {
+
+
+ }
+
+ void CreateMask()
+ {
+ _mask = new Mat(13005 + 256, 256, MatType.CV_8UC1, (Scalar)0);
+ }
+
+ public SpatialFilterItemGPU(CategoryItem parent) : this()
+ {
+ Parent = parent;
+ }
+
+ public Mat Mask
+ {
+ get
+ {
+ if (_mask == null)
+ {
+ if (FilePath != null)
+ {
+ ReloadMask();
+ }
+ else
+ {
+ CreateMask();
+ }
+ }
+
+ return _mask;
+ }
+ set
+ {
+ _mask = value;
+ }
+ }
+
+ private static Mat? _lastLoadedImage;
+
+
+ public unsafe void Transform(ref Mat image, ref Mat res)
+ {
+
+
+
+ var w = image.Width;
+ var mw = Mask.Width;
+
+
+
+ if (_lastLoadedImage != image)
+ {
+ var imageBytes = new ReadOnlySpan(image.DataPointer, image.Width * image.Height * 3);
+ _imageMemory ??= Kernels.Accelerator.Allocate1D(imageBytes.Length);
+ _imageMemory.View.BaseView.CopyFromCPU(imageBytes);
+ _lastLoadedImage = image;
+ }
+
+ var len = (int)_imageMemory.Length;
+
+ _resMemory ??= Kernels.Accelerator.Allocate1D(len / 3);
+
+
+
+ Kernels.SpatialKernel(len/3, w, mw, _imageMemory.View, _maskGpu.View, _resMemory.View);
+ var arr = _resMemory.GetAsArray1D();
+
+
+ var candidates=new Mat(new[]{image.Height,image.Width}, MatType.CV_8UC1);
+ candidates.SetArray(arr);
+
+
+ if (Subtract && !IsSelected)
+ {
+ res -= candidates;
+ }
+ else
+ {
+ res |= candidates;
+
+ }
+
+
+ candidates.Dispose();
+
+
+
+ }
+
+ public void Save(BinaryWriter writer)
+ {
+ writer.Write(FilePath ?? "");
+ writer.Write(Subtract);
+ }
+
+ public void Load(BinaryReader reader)
+ {
+ FilePath = reader.ReadString();
+ Console.WriteLine("Loaded path:" + FilePath);
+ if (FilePath.StartsWith("..\\../"))
+ FilePath = FilePath.Replace("..\\../", "..\\");
+ Subtract = reader.ReadBoolean();
+ }
+
+ public bool IsSelected { get; set; }
+
+
+ public override string ToString()
+ {
+ var totalColors = 0;
+ if (Mask != null)
+ {
+ totalColors = Mask.CountNonZero();
+ }
+ return $"{(Subtract ? "-" : "+")}Spatial Filter({totalColors} colors)";
+ }
+
+
+}
\ No newline at end of file
diff --git a/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/SubtractFilterItem.cs b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/SubtractFilterItem.cs
new file mode 100644
index 0000000..04c1944
--- /dev/null
+++ b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Filters/SubtractFilterItem.cs
@@ -0,0 +1,52 @@
+using OpenCvSharp;
+
+namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
+
+public class SubtractFilterItem : IFilter,IFilterParent
+{
+ public IFilterParent Parent { get; set; }
+
+ public List Filters { get; set; } = new List();
+
+ public void Transform(ref Mat image, ref Mat res)
+ {
+ using var tracker = new ResourcesTracker();
+ var temp = tracker.NewMat(res.Size(),MatType.CV_8UC1,Scalar.Black);
+ foreach (var filter in Filters)
+ {
+ filter.Transform(ref image, ref temp);
+ }
+ res = res - temp;
+ temp.Dispose();
+ }
+
+ public void Save(BinaryWriter bw)
+ {
+ bw.Write(Filters.Count);
+ foreach (var filter in Filters)
+ {
+ bw.Write(filter.GetType().FullName);
+ filter.Save(bw);
+ }
+ }
+
+ public void Load(BinaryReader reader)
+ {
+ int count = reader.ReadInt32();
+ for (int i = 0; i < count; i++)
+ {
+ var type = reader.ReadString();
+ var filter = (IFilter)Activator.CreateInstance(Type.GetType(type));
+ filter.Load(reader);
+ filter.Parent = this;
+ Filters.Add(filter);
+ }
+ }
+
+ public bool IsSelected { get; set; }
+
+ public override string ToString()
+ {
+ return "Subtract";
+ }
+}
\ No newline at end of file
diff --git a/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/HSIValue.cs b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/HSIValue.cs
new file mode 100644
index 0000000..f700d79
--- /dev/null
+++ b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/HSIValue.cs
@@ -0,0 +1,125 @@
+using System.ComponentModel;
+using System.Drawing;
+using System.Runtime.CompilerServices;
+using System.Xml.Serialization;
+
+namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses
+{
+ public class HSIValue
+ {
+
+ public HSIValue()
+ {
+
+ }
+ public int MinSize { get; set; } = 1;
+ public int MinComponentSize { get; set; } = 1;
+ public int PreFilterId { get; set; } = 0;
+ public int PreFilterSize { get; set; } = 10;
+
+ public int Closing { get; set; }
+
+ public int Circle { get; set; } = 1;
+ public string Tag { get; set; }
+ private Range _hue = new Range() {Minimum = 0,Maximum = 10};
+ private Range _saturation = new Range() { Minimum = 0, Maximum = 10 };
+ private Range _intensity = new Range() { Minimum = 0, Maximum = 10 };
+
+ public string Name { get; set; }
+
+ public Range Hue
+ {
+ get { return _hue; }
+ set
+ {
+ if (Equals(value, _hue)) return;
+ _hue = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public Range Saturation
+ {
+ get { return _saturation; }
+ set
+ {
+ if (Equals(value, _saturation)) return;
+ _saturation = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public Range Intensity
+ {
+ get { return _intensity; }
+ set
+ {
+ if (Equals(value, _intensity)) return;
+ _intensity = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public bool Active { get; set; }
+ public List SubColors { get; set; }=new List();
+ public Rectangle GetRectangle()
+ {
+ Rectangle th = new Rectangle((int) (Hue.Minimum), (int) (Saturation.Minimum),
+ (int) (Hue.Maximum) - (int) (Hue.Minimum), (int) (Saturation.Maximum) - (int) (Saturation.Minimum));
+ return th;
+ }
+ [XmlIgnore]
+ public bool ShowStar { get; set; }
+
+ public int Dilation { get; set; }
+
+
+ public override string ToString()
+ {
+ return $"{Name} {Hue}:{Saturation}:{Intensity}{(ShowStar?"*":"")}";
+ }
+
+
+
+ public static explicit operator ColorConfig(HSIValue value)
+ {
+ var config = new ColorConfig()
+ {
+ ClosingCircleRadius = value.Circle,
+ HueHighSaturationMaxGrey = value.Hue.Maximum,
+ HueHighSaturationMinGrey = value.Hue.Minimum,
+ HighSaturationMaxGrey = value.Saturation.Maximum,
+ HighSaturationMinGrey = value.Saturation.Minimum,
+ IntensityMaxGrey = value.Intensity.Maximum,
+ IntensityMinGrey = value.Intensity.Minimum,
+ MaxBlobSize = value.MinSize,
+ MedianMargin = 1,
+ MedianRadius = 10,
+ UseMedianFilter = true,
+
+ };
+ return config;
+ }
+
+ public static implicit operator HSIValue(ColorConfig config)
+ {
+ return new HSIValue()
+ {
+ Hue = new Range(config.HueHighSaturationMinGrey, config.HueHighSaturationMaxGrey),
+ Saturation = new Range(config.HighSaturationMinGrey, config.HighSaturationMaxGrey),
+ Intensity = new Range(config.IntensityMinGrey, config.IntensityMaxGrey),
+ Circle = (int)config.ClosingCircleRadius,
+ MinSize = config.MaxBlobSize
+ };
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+
+ protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+ }
+}
\ No newline at end of file
diff --git a/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Kernels.cs b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Kernels.cs
new file mode 100644
index 0000000..8d64af9
--- /dev/null
+++ b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Kernels.cs
@@ -0,0 +1,157 @@
+using ILGPU;
+using ILGPU.Runtime;
+using OpenCvSharp;
+
+namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses;
+
+public static class Kernels
+{
+ static Kernels()
+ {
+ Init();
+ AppDomain.CurrentDomain.ProcessExit +=
+ Destruct;
+ }
+
+ private static void Destruct(object? sender, EventArgs e)
+ {
+ _accelerator.Dispose();
+ _gpuContext.Dispose();
+ }
+
+
+ private static object _lock = new object();
+ private static ILGPU.Context _gpuContext;
+ private static Accelerator _accelerator;
+ private static Action, ArrayView, ArrayView> _spatialKernel;
+ private static Action, ArrayView> _dilationKernel;
+
+ public static Action, ArrayView, ArrayView> SpatialKernel => _spatialKernel;
+
+ public static Action, ArrayView> Dilation => _dilationKernel;
+
+ public static Accelerator Accelerator
+ {
+ get => _accelerator;
+ set => _accelerator = value;
+ }
+
+ static Dictionary> _imageCache = new ();
+
+ public static unsafe MemoryBuffer1D TransformImageCached(Mat image)
+ {
+ if (_imageCache.ContainsKey(image))
+ return _imageCache[image];
+ var imageBytes = new Span(image.DataPointer, image.Width * image.Height * 3);
+
+ var res=Kernels.Accelerator.Allocate1D(imageBytes.ToArray());
+ _imageCache[image] = res;
+ return res;
+ }
+
+
+ static void Init()
+ {
+ lock (_lock)
+ {
+
+ if (_gpuContext != null) return;
+
+ _gpuContext = ILGPU.Context.Create(builder => builder.AllAccelerators());
+ _accelerator = _gpuContext.Devices.First(x => x.AcceleratorType == AcceleratorType.Cuda)
+ .CreateAccelerator(_gpuContext);
+ Console.WriteLine("GPU acceleration initialized");
+
+
+ _spatialKernel = _accelerator
+ .LoadAutoGroupedStreamKernel, ArrayView, ArrayView>(
+ Kernels.TransformSpatialKernel);
+
+ _dilationKernel = _accelerator
+ .LoadAutoGroupedStreamKernel,ArrayView>(
+ Kernels.DilationKernel);
+
+ }
+ }
+
+ private static void TransformSpatialKernel(Index1D index, int w, int mw, ArrayView candyBytes, ArrayView maskBytes, ArrayView res)
+ {
+ var i = index;
+ var p = i * 3;
+ var y = p / w;
+ var x = p % w;
+
+
+
+ var yp = (int)candyBytes[p + 0];
+ var xp = candyBytes[p + 1];
+ var vp = (byte)(candyBytes[p + 2] / 5f) * 255;
+
+ var hp = yp + vp;
+ var maskColor = maskBytes[hp * mw + xp];
+ if (maskColor > 0)
+ res[i] = 255;
+ else
+ res[i] = 0;
+ }
+
+ private static void DilationKernel(Index1D index, int w, int size, ArrayView maskBytes,
+ ArrayView res)
+ {
+ var i = index;
+ var p = i ;
+ var y = p / w;
+ var x = p % w;
+
+
+ var halfSize = size-1 / 2;
+
+
+ if (maskBytes[p] > 0)
+ {
+ for (int j = -halfSize; j <= halfSize; j++)
+ {
+ for (int k = -halfSize; k <= halfSize; k++)
+ {
+ var yp = y + j;
+ var xp = x + k;
+ if (yp < 0 || yp >= w || xp < 0 || xp >= w)
+ continue;
+ res[yp * w + xp] = 255;
+ }
+ }
+ }
+
+ }
+
+ private static void ErosionKernel(Index1D index, int w, int size, ArrayView maskBytes,
+ ArrayView res)
+ {
+ var i = index;
+ var p = i ;
+ var y = p / w;
+ var x = p % w;
+
+
+ var halfSize = size-1 / 2;
+
+
+ if (maskBytes[p] <255)
+ {
+ for (int j = -halfSize; j <= halfSize; j++)
+ {
+ for (int k = -halfSize; k <= halfSize; k++)
+ {
+ var yp = y + j;
+ var xp = x + k;
+ if (yp < 0 || yp >= w || xp < 0 || xp >= w)
+ continue;
+ res[yp * w + xp] = 0;
+ }
+ }
+ }
+
+ }
+
+
+}
\ No newline at end of file
diff --git a/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Range.cs b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Range.cs
new file mode 100644
index 0000000..9c5361d
--- /dev/null
+++ b/Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Range.cs
@@ -0,0 +1,64 @@
+namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses
+{
+ /// The Range class.
+ /// Generic parameter.
+ public class Range where T : IComparable
+ {
+
+ public Range()
+ {
+
+ }
+ public Range(T min,T max)
+ {
+ this.Maximum = max;
+ this.Minimum = min;
+ }
+
+ /// Minimum value of the range.
+ public T Minimum { get; set; }
+
+ /// Maximum value of the range.
+ public T Maximum { get; set; }
+
+ /// Presents the Range in readable format.
+ /// String representation of the Range
+ public override string ToString()
+ {
+ return string.Format("[{0} - {1}]", this.Minimum, this.Maximum);
+ }
+
+ /// Determines if the range is valid.
+ /// True if range is valid, else false
+ public bool IsValid()
+ {
+ return this.Minimum.CompareTo(this.Maximum) <= 0;
+ }
+
+ /// Determines if the provided value is inside the range.
+ /// The value to test
+ /// True if the value is inside Range, else false
+ public bool ContainsValue(T value)
+ {
+ return (this.Minimum.CompareTo(value) <= 0) && (value.CompareTo(this.Maximum) <= 0);
+ }
+
+ /// Determines if this Range is inside the bounds of another range.
+ /// The parent range to test on
+ /// True if range is inclusive, else false
+ public bool IsInsideRange(Range range)
+ {
+ return this.IsValid() && range.IsValid() && range.ContainsValue(this.Minimum) && range.ContainsValue(this.Maximum);
+ }
+
+ /// Determines if another range is inside the bounds of this range.
+ /// The child range to test
+ /// True if range is inside, else false
+ public bool ContainsRange(Range range)
+ {
+ return this.IsValid() && range.IsValid() && this.ContainsValue(range.Minimum) && this.ContainsValue(range.Maximum);
+ }
+
+
+ }
+}
\ No newline at end of file
diff --git a/Hawkeye.VisionBuilder.Workflow/Configuration/BadTweakLearning.cs b/Hawkeye.VisionBuilder.Workflow/Configuration/BadTweakLearning.cs
new file mode 100644
index 0000000..e966408
--- /dev/null
+++ b/Hawkeye.VisionBuilder.Workflow/Configuration/BadTweakLearning.cs
@@ -0,0 +1,20 @@
+namespace Hawkeye.VisionBuilder.Workflow.Configuration;
+
+public class BadTweakLearning
+{
+ public bool Enabled { get; set; } = false;
+ public int ThresholdLevel { get; set; } = 80;
+
+ public void Save(BinaryWriter bw)
+ {
+ bw.Write(Enabled);
+ bw.Write(ThresholdLevel);
+ }
+
+ public void Load(BinaryReader br)
+ {
+ Enabled = br.ReadBoolean();
+ ThresholdLevel = br.ReadInt32();
+ }
+
+}
\ No newline at end of file
diff --git a/Hawkeye.VisionBuilder.Workflow/Configuration/ECameraType.cs b/Hawkeye.VisionBuilder.Workflow/Configuration/ECameraType.cs
new file mode 100644
index 0000000..5fc0a2c
--- /dev/null
+++ b/Hawkeye.VisionBuilder.Workflow/Configuration/ECameraType.cs
@@ -0,0 +1,8 @@
+namespace Hawkeye.VisionBuilder.Workflow.Configuration;
+
+public enum ECameraType
+{
+ Emulation=0,
+ Hawkeye=1
+
+}
\ No newline at end of file
diff --git a/Hawkeye.VisionBuilder.Workflow/Configuration/EOutputs.cs b/Hawkeye.VisionBuilder.Workflow/Configuration/EOutputs.cs
new file mode 100644
index 0000000..3a833e6
--- /dev/null
+++ b/Hawkeye.VisionBuilder.Workflow/Configuration/EOutputs.cs
@@ -0,0 +1,8 @@
+namespace Hawkeye.VisionBuilder.Workflow.Configuration;
+
+public enum EOutputs
+{
+ None,
+ Firmata,
+ NetServer
+}
\ No newline at end of file
diff --git a/Hawkeye.VisionBuilder.Workflow/Configuration/RecipeSelectionConfig.cs b/Hawkeye.VisionBuilder.Workflow/Configuration/RecipeSelectionConfig.cs
new file mode 100644
index 0000000..589c78a
--- /dev/null
+++ b/Hawkeye.VisionBuilder.Workflow/Configuration/RecipeSelectionConfig.cs
@@ -0,0 +1,19 @@
+namespace Hawkeye.VisionBuilder.Workflow.Configuration;
+
+public class RecipeSelectionConfig
+{
+ public bool SerialEnabled { get; set; } = false;
+ public string ComPort { get; set; } = "COM3";
+
+ public void Save(BinaryWriter bw)
+ {
+ bw.Write(SerialEnabled);
+ bw.Write(ComPort);
+ }
+
+ public void Load(BinaryReader br)
+ {
+ SerialEnabled = br.ReadBoolean();
+ ComPort = br.ReadString();
+ }
+}
\ No newline at end of file
diff --git a/Hawkeye.VisionBuilder.Workflow/Configuration/WorkflowConfiguration.cs b/Hawkeye.VisionBuilder.Workflow/Configuration/WorkflowConfiguration.cs
new file mode 100644
index 0000000..8ffdaa0
--- /dev/null
+++ b/Hawkeye.VisionBuilder.Workflow/Configuration/WorkflowConfiguration.cs
@@ -0,0 +1,58 @@
+using System.Reflection.Metadata.Ecma335;
+using System.Security.AccessControl;
+using Hawkeye.VisionBuilder.Workflow.Operations;
+
+
+namespace Hawkeye.VisionBuilder.Workflow.Configuration;
+
+public class WorkflowConfiguration
+{
+ public WorkflowConfiguration()
+ {
+ var userFolder = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
+ PythonPath = Path.Combine(userFolder, @"miniconda3\envs\ai3_cpu\python.exe");
+ }
+
+ public ECameraType RuntimeCameraType { get; set; } = ECameraType.Emulation;
+ public ECameraType DevelopmentCameraType { get; set; } = ECameraType.Emulation;
+
+ public string EmulationPath { get; set; } = "..\\Data\\Emulation";
+ public string PythonPath { get; set; }
+
+ public EOutputs Outputs { get; set; }
+ public int ResultPin { get; set; }
+ public string SerialPort { get; set; } = "";
+ public int Delay { get; set; } = 0;
+
+
+ public BadTweakLearning BadTweakLearning { get; set; } = new BadTweakLearning();
+ public RecipeSelectionConfig RecipeSelectionConfig { get; set; }=new RecipeSelectionConfig();
+
+ public void Save(BinaryWriter bw)
+ {
+ bw.Write((int)RuntimeCameraType);
+ bw.Write((int)DevelopmentCameraType);
+ bw.Write((int)Outputs);
+ bw.Write(EmulationPath);
+ bw.Write(PythonPath);
+ bw.Write(ResultPin);
+ bw.Write(SerialPort);
+ bw.Write(Delay);
+ BadTweakLearning.Save(bw);
+ RecipeSelectionConfig.Save(bw);
+ }
+
+ public void Load(BinaryReader br)
+ {
+ RuntimeCameraType = (ECameraType) br.ReadInt32();
+ DevelopmentCameraType = (ECameraType) br.ReadInt32();
+ Outputs = (EOutputs) br.ReadInt32();
+ EmulationPath = br.ReadString();
+ PythonPath = br.ReadString();
+ ResultPin = br.ReadInt32();
+ SerialPort = br.ReadString();
+ Delay = br.ReadInt32();
+ BadTweakLearning.Load(br);
+ RecipeSelectionConfig.Load(br);
+ }
+}
\ No newline at end of file
diff --git a/Hawkeye.VisionBuilder.Workflow/Context.cs b/Hawkeye.VisionBuilder.Workflow/Context.cs
new file mode 100644
index 0000000..de08597
--- /dev/null
+++ b/Hawkeye.VisionBuilder.Workflow/Context.cs
@@ -0,0 +1,13 @@
+using Hawkeye.VisionBuilder.Workflow.Datatypes;
+using Hawkeye.VisionBuilder.Workflow.Datatypes.Elements;
+
+namespace Hawkeye.VisionBuilder.Workflow;
+
+public class Context
+{
+ public HawkeyeImage? ActiveImage { get; set; }
+ public HawkeyeImage? LastCameraImage { get; set; }
+ public List GraphicsElements { get; set; }=new List();
+ public Dictionary Memory { get; set; } = new Dictionary();
+
+}
\ No newline at end of file
diff --git a/Hawkeye.VisionBuilder.Workflow/DataTransfer/CSharpDataTransferMQ.cs b/Hawkeye.VisionBuilder.Workflow/DataTransfer/CSharpDataTransferMQ.cs
new file mode 100644
index 0000000..d776dd8
--- /dev/null
+++ b/Hawkeye.VisionBuilder.Workflow/DataTransfer/CSharpDataTransferMQ.cs
@@ -0,0 +1,55 @@
+using NetMQ;
+using NetMQ.Sockets;
+
+namespace Hawkeye.VisionBuilder.Workflow.DataTransfer;
+
+public class CSharpDataTransferMQ
+{
+
+ private object _syncObject = new object();
+ private readonly ResponseSocket _server;
+
+ public CSharpDataTransferMQ()
+ {
+ this._server = new ResponseSocket();
+ _server.Bind("tcp://*:5555");
+
+ }
+
+ public byte[] TransferData(string command, byte mode, byte[] data)
+ {
+ lock (_syncObject)
+ {
+ return TransferDataInternal(command,mode,data);
+ }
+
+ }
+
+ private byte[] TransferDataInternal(string command,byte mode,byte[] data)
+ {
+ Console.WriteLine("Waiting for request");
+ _server.ReceiveFrameBytes();
+
+ MemoryStream ms = new MemoryStream();
+ BinaryWriter bw = new BinaryWriter(ms);
+ var functionBytes=System.Text.Encoding.ASCII.GetBytes(command);
+ bw.Write(functionBytes.Length);
+ bw.Write(functionBytes);
+ bw.Write(mode);
+ bw.Write(data);
+
+
+
+
+
+ Console.WriteLine("Sending response");
+ _server.SendFrame(ms.ToArray());
+
+ Console.WriteLine("Waiting for request");
+ var result= _server.ReceiveFrameBytes();
+
+ _server.SendFrame("OK");
+
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/Hawkeye.VisionBuilder.Workflow/DataTransfer/CSharpDataTransferMQRPC.cs b/Hawkeye.VisionBuilder.Workflow/DataTransfer/CSharpDataTransferMQRPC.cs
new file mode 100644
index 0000000..7b3fd2e
--- /dev/null
+++ b/Hawkeye.VisionBuilder.Workflow/DataTransfer/CSharpDataTransferMQRPC.cs
@@ -0,0 +1,130 @@
+using MessagePack;
+using NetMQ;
+using NetMQ.Sockets;
+
+namespace Hawkeye.VisionBuilder.Workflow.DataTransfer;
+[MessagePackObject]
+public class MethodCall
+{
+ [Key(0)]
+ public string Name { get; set; }
+ [Key(1)]
+ public List