From 86ec50575bc6d9f430f9210e8668f1ab4b59b4d9 Mon Sep 17 00:00:00 2001 From: uvos Date: Thu, 17 Jun 2021 18:39:47 +0200 Subject: [PATCH] mainny ui improcements --- res/icon.png | Bin 0 -> 46116 bytes res/resources.qrc | 1 + src/camera.cpp | 2 +- src/cameras.cpp | 75 +++++++++++---- src/cameras.h | 5 +- src/imagepipeline.cpp | 110 ++++++++++++++++++---- src/imagepipeline.h | 1 + src/main.cpp | 27 +++++- src/profile.cpp | 141 ++++++++++++++++------------ src/profile.h | 13 +-- src/ui/cameradialog.ui | 8 +- src/ui/cameralistwidget.cpp | 4 +- src/ui/configurecameradialog.cpp | 74 ++++++--------- src/ui/configurecameradialog.h | 10 +- src/ui/configurecameradialog.ui | 154 ++++++++++++++++--------------- src/ui/cvimageviewer.cpp | 81 ++++++++++++++-- src/ui/cvimageviewer.h | 15 +++ src/ui/editprofiledialog.cpp | 99 ++++++++++++++++++-- src/ui/editprofiledialog.h | 8 +- src/ui/editprofiledialog.ui | 93 ++++++++++++++++++- src/ui/mainwindow.cpp | 60 +++++++++++- src/ui/mainwindow.h | 6 ++ src/ui/mainwindow.ui | 86 ++++++++++++++++- src/ui/profiledialog.cpp | 2 +- src/ui/profiledialog.ui | 8 +- 25 files changed, 819 insertions(+), 264 deletions(-) create mode 100644 res/icon.png diff --git a/res/icon.png b/res/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..feae36f4e8b82852beb62072e2dca9ad890cb272 GIT binary patch literal 46116 zcmaI7eO!|F9{+FKsx3=Tr-Bt}|4(+-x;2~DgoTBz`|^uVzYYsq5&F6!?DbWlMPAt-sL|pulCyEQhfX6YX3W24^PZXahEd_pxIaYD1u%sLZ%qckm-1jl? z#Hj*wAr82|w739wJn?98Vad1K`ZzB_yLjv6E+ zzB4udv*ABN+x+e2Hs72$RT!3(l>UFeK3ovLKk)Lr{yK%Sg1=UNc<-yIcSD2gUk>gL zjg=LI{hvi*QqqTi*BqjSR))V^x&K&UNnzMupZa(5uY`s-yd3^IbYg{N|L>s^cciQ> zym=%n?Cr2GKmGWdsuiu1hb}m12b95Aa$~Va04rZR+<#)#d#6tyk3YKewPQ#3KE8kd z)9Y{XZJ%HSI}n>W7Z5*0|M(T~?%%%p^dEbF`y~A0$KL`rtyuXf7U0gb>=(iPBX$o3 z1`(A6Gt$r&f%EW5S~eudm{u|O?52xURplOxZph_pmjzleFfapz{_>e1i38WoOwJEa zeG@&8l>=hxN8@2M;xi+TQ9zh0!*(@@Vl6EevQf-*Z=lUgpUrfiJ*oYhQRAWoT{Q6_ z`nFC%zu33*V?Ex08ocbp+JyOPyXFrCsAo$v$M@8)4KCB-sEey$Z)E0!xSLT3Cjy(3!1(zyxy{ z7m1-QtK+;AZ(5jl2ag_&A#fXu4`>S`Z@&eGB6^c9VWw)?dQX zqTAAk*YYy~DE99XWFi8D0}#18+F}y(s+oj}d6(wJ%uIvDGxpx_lhn=R<)ZYeG&736 z14-96<`+nfys@HN*jwQflbxx;3$zWQ%8H}df@Z>F2E|=8KP=K;%Jf`fV=j`aSLOdb z>8Uj3$;tEwJ-*U*+vVyK%5in>)fVGH4Fp3^Y4+R?-i`F#RR_N&)dfcXF6Vk~)kPu` zTKQO~Y3akCPP7>BH{0(C9;G_%hmh%X;#-uy#PXgUjM(!Z{#; z^A?ruB*NX7>fuf2wNdA<@xMvTO4vFQU74|eOg>2jx5TvEC_kFfjTSB zqCU@p5||l_a86=$zvbWciuoMRkF=xxc6v)dR-1zBcJ#rG-1;>Nigzi~u|LzXs7D6$ zk(suwnPq2(h5EU-T}eIS7t)T~uS?D(Sm@Kg5E5@7^1G;bd(!oMd7AjzB*G8Mw1Xl= zi{YYDf^gWvZ&TYdaDXb5e27L$Zoj~m^xJ4r=q{{7$wGn-+CLF_ur_bYJ&r$gf#)}4 zdi#O@4W8Hiz}{ro+k<|PJ0V9I$V*o1t!T~zOl6c5L;d|sudHg(i`wjR-tb_8CK1GU zALhG1tU??RmSWGo%Pd&M!N0-I&gv(FL5-u0aP?YI>y7EQ%rrwG*Jl1tv;Dtf&Qa^6 z7?xdovZnZe$cJO9Ze|e3i~7?3N7JbI66(2{^GnWq-!3ow)Xc^ne{Lk7z&@oz<##4X zzDw5?XY04fhFWqWG8JdGHA|-xD;LG8JOO1wY450S05nK)|K@yS^S7WIg{zYCs*U3H zK0|y;WWk6ard~O9nX@?94fV+SwzrGU0Nc~0N98EQ0~H_V#R=Uu>k$4%Npud`||;EssR=M<0K3m=0V|dkoTFsfD44v=qb-*q-xcnx zdeZtO*E^H5`tAqzU)Pa9of*#x@A0Stnx7MNMAE#l;B3tfIyIRUnK+Snm|$exh`9g| z@!vwkL}cJw06PnthMC5ha^G>ROu+{z4df$gPCU3Jy!Ar=)$vvzMeCwO{6DgaMw_!fo!60jARz4}9OU8j^xM!9#AaEu3DKl~!sMG8#Ns@|3679IHv5@~ zfPegGe$T7oLlo{M{88D7suns|!nzFe+bTo<0gb_<*_nP7lj8kw^ri{-#Q4-h_*)8$ znLNL*6@w4k^cuu&tD(NA zfs*G~)H%~NinboJlQl~UBBX+=hnk9n%0ypfon-!B!!3kfCkmFB2Arl*nVS$sJU@-n`glx;1k|6%#mpB8tWrOG|a49uo1 z+`BydjfLtsFNu2zrZl?>-hV`c4L1I`8;{=pjN%3$B(5*7CObp3PyO>j$2r z2&3xt2k!3Tlvv$MwKey26XEpRTaq>j;Z=0y%|d`rzu0Eo#mR#0a+(%1kiLHr6c-)6 zq$on=>c6q#(9X6GnJC3)d=mOF7*IkbGXX2Yeuxe04LbVEuY+GXC}!`r&2%zyQ;Zeo zfZdMXVIc@u4`8WA4AXOl{9TpRBrkmvUk1k7+eUN)a)At%7tAXAAqFIHfY#~3pq5#zp$l>DJd3Z|G z8>Dt()1?ig7tDw{<682@>*_;ekcdE|&gB`oRBfq}WB{Pn4Vl)@r)%Rlam_S~i)tBm znF=-wS(asQqJah9L@zr-I*FJc?%m!ld<2KqLIe?STO;PS+yVLkO-r9#qe908hq&)v z>|>x#jv0jR%z!R4FbxYl%#3ktG$bqY`?#}&b1jWbqo9#r85Kn2$=uD>YGW6-4PReh zLzd4GEXDIi!~A@4Gc4#RL(xef^oK0VR) zEcRtyU_|R(Ji&CHNOAvL8;$2M{CjA#z)7n2O{`#e>6LyVC)kZy?%vG8{GKwx<8;Qk z&&2gmf=oO@rZ1NwnfmWDBsN0hw4nUAIKGi%1ZjSp z<8piBSZ@;=3@u>y56DI(09CO=HXxoV>H~2i*F|tlp`)7%7QYy2FZ;QBPjK0lch{^Vl_7JnjmIm$i0gKka!=p=bR%=s z8+|J%{~==K1lW}T;(rkTOeJu$X7%mVG_~an=6N){Ho^5M{K^Buw^WwoytbJMHXat{ zqlkw+Ygp6p>ff)cV(Uc7TOwoqN;(EQv*zRj4CVN)VV!BA6((mWjq)=3$8ts9E;d{W zr`z|!8Sp-E|I%j^;rc9hQFpiH{?ZNxcfOf<5VE(K+?mXUzNGKG6SoG}^mAi1JSUT)$%FegnIj8bga>iVr(~K5h z3oC1dp{#xvG-%HZR?fchG1Wj!S?pfu8OXm{@}VP2+dAQUP7jqZ%c-dxC^eamkhAV) z5)3;y#Dhe~O;OX};5Perv%>?o!UdH1-yaaZ0VA3n|M4LHrz7RqQyo~-|1LGua>pQw zR2e<1x*?SAP3iuWp3~<%{zKupxHO)_)m!%Z%U73fTAv6o3`|U99>#I6eABG7C%FFB z9{;s-dcCM;QdOQceGPz2i=Yy)#-#eqD4IWJ=k2t5knSE~UQaGnvUpUGsDs}Qu3T-p zdA0$+3W>>Z&*=sZun>TSf2ozNO)T#mnS5Y#b+}QvaDOIUXXsf;t*}Flj|RQMTQF{* z2;gnDVwJ_aMQ9L`Nvg;rNs9n_Rz}#{AgPp+L>#z|ygl2nl^=21AVV>%%L791XSFE# z<1M>0NT+CTktw$_kfQKKW?-1s2eJ3reQOTK{jjYr@+aRPWdDYX67NkZOTM-Q?I*VI zz2BKT`)M+K@*N{16+YJ2%(2MX9xWZ}|Gfpu zClPv;6uDlX2~0QM!fynPG1g`6Qs*VOb@qwgz9DqyAkzEc+kgLgB6_~wKUNPR>)}+r zDeDX*7XP=VlxBkSf-%znIC2t^TS}Z=o(bN-cyFlVetZ#~3a%}R<`6MOPczpD2n5{% z{>BZ+@_rHrUtM<1$kO&6@=U~4lgxQ^^Yi~@z8X)79H@f^4;Py;tR&s^$O@gkC|!ug1>yqxckK0~OMKC#i|6#hDk@ z)U-ADPauh}b<}9mkBZ)LysX*;{)5*8y$4MrECYA~(Aq~DrEX)7zWp2rY^tcwqXXbg z`ibTmi)#4I=9cqJsAk0R3XflbYic<+%3P5vDE)?&mq>C)JSU(@Yx-T?!D-d> z645;0ZC`d+4N+5pI!@ImAza?=J0j|-^okHPpiiqAcCt92TwG3~gNa_SdP ziEwho(m%BEe0R=6>W#+rwnX*HAsNSwLK}eg|28P1d5yU3NP(}1*vH`Wz0N{E9Nj=? zhHnq70wcOW-!wlg?{VyuMjFrV*GzTx_U4yv>RH<`ecMmu@4&=@+z%h>=(C=A>37+f z1LpNJ(Ud3A5Q?vK7soY&KBZ{X-8=B=wrDsy5($W)dM;dIqO`IfB`D;A{5HTNx!gR7 zSp7IUVDUgi##!1||NG&>4@abLWQdQ}sC!jM=k)9p-Q`gu;-cbu#%X`t+Y_zZM=&2I zm+(K?)i5oE16Lmq^3>ZWM;tqwwQ$XBK2WYYudA#}gk$y76IPi;e`%O@L{RKX2^zHI zF7taAjC-bP9~^3~g=|>plBXe>nfz%Rh*hzSn^umbE7iOwed zQ@mZJ8rhg|U<;KBtCuc6d)C|%GfJ@iU3yOSJBy8M^B7v}yGDVM)?#7Tr2PA>x+~%C zo7ZXILJ(ql&5fIleM6*qB|^!n-@;$&XZ0NnCG7k_ZB8@=V5&ba&fa&mLtH=?T<-w#&ZEy*nHYK{EIhWj$vYcjzF-ll`96}JduZI}b0 zU>eUdT!#}ksqzrMoMm}hz0Lg8!iVI~^e&Wa%4koGr};TW2-$&tV^W5z?Fv5qFf|lZ z^aS+D{!u$!^2YpkJbKcydwMi8npHyTBOP?hmq^{iuf|Ezq$u_oJ2b6_Jjbr}YoS|w zyU5tz8<51o&wkzW%Ic3$w?S8TKbk*_0}k&LjYpZc$JdPH#c`%qk4mvaw+4^Sfh6m6 zITXZDskByEyNw3gv|K8V9_Eam{o|Xf zNhqPb3kUK%e<3ZGhN3yOutC2-M;SY0tj}SrOdOy9Guv4C?XN2rf5@Nfr=zMeblA&; zhy4u=D#JF8G3jtDr8iYUa+Xg|a-F_=(KKtcXj*rdO*T(;su=7c-E-u$^eZ)omLr@2 zI#F|z>w`I1joD4TGr@I$7p`!~%;pxdus>7I1`oj%B($epD~L%5TwY}yGR7=f-3i@Q zOtJrp4hpwXk#sGzc*+xokF5MaI)VOfTkH_&H6caFZp0 z=HLXBHd7dHri-22QHc85k4RpOdm2ekJLR37jXm@WcO8|j*7 zvcKZIzp!8_sO1=LK|Al9t5+X$8QNhN>vg@ymWK&w<;plmbFba`S))Qd z3T!_-us|Q|Z9*1Ea~fa@!B?_D2)~ztrZ~X^OtmzU){6p{&ZT(&p^2#{5$)h-Hg7w0 zqOJ#}D473@3pIe{DrMtw_hn8MSg!|mQz_XcbUvGbNjLt$iWo)C&X#5eW{3G5cw_yg zQI5rQoz~)cFejrMziC-+>OVWECz1aC(??LuoBWBvi-RCVENZ=ZuBmNRv=BrsAf4eH zpd|MXEJ|f5OmxpWW07%+^cKd*$yDs_kIB;5?>&0uMni*G+sYp+!^%Am)0Q23Ny$e4 z#KMF$MRqx&JDv2%0QV-L^C3g#VFjN&S87tHtwlTL^pZWeRp#?d3V(l`bLs0NuMr+9 z7i}X_?wcDqe;BcYALb$tdqXM0;FUNzm(S0&k4HryLrEVuK7ygjdC*V1-PPEUi^$9G zwhv|G25=5#J7oX3zF5E=B>ntv3_zS!C+ebhDbK|sDVzC~pK(FS>mphJkyI3}H!Z=^ z(KgTdE^FeV3ELztB*ANWeMywV-u)5r|t*EV~umO%k~n&-;GV!=o$bXQ#ZR!|7yj&la!VO3lgU zYGDS)2Ta=sJl;Tr_J{ekl_tJ-4K1qME=VX!WB5(!^QPu-ueVKRGS;s~uDftRIN$=q z9XC}FcNu90|3ft~P{^OU!!Q z-RGA`GyRt5Bf&rGeSeCzzGj+zUB;;7)EYK5XKc3%wR64~4TV&GevB#P^8;@WF+Cuf zL8boUWSrvmCKg$9%T z-d*J|tv|iu!;fF%&K*V1-5%R?06{k%jgLlK+lFVpv48h)@lH}qw|5Q)u=wH(^v+CG9jT%B;Yq^hyxrzib8mfpde{* z%ch~gEB2hR1SEFA8SAi$r@SpWn5pWUa91p^5%e@Mc zxBmir0yR%R~n4RLJVKlFNgSvyMUTHL=UJ|)xjqCqT41d*D>rOhTVS)kmp zg~6zq43+6SB@GS8$x9XfO{YXhi2;Y+z8z9;y%*=Z$761_ciNXmrSV$N0O1$jjmx9Q zI_I`+S&LtuOJqX`ZmAes;@yBB*&#p5kKV77!t<7gmcZYpa!Q-Z+{3uAb?Xbi<=Ub) zQma?a=j1PrQ3nJF~_^1Bgox7`B&)f#`?Bd*K3WiHA5#(M(KY=m5 zfZtrDd{Kcm_=w+}Gf#Re_;K9Q$GnEI?qh<5Vq|~&ZxFD|qh8T4-K-Fxc9!ws*b`Fu zD&79-7HYK#)5Q_McJSg5M*9l=zG*}8 z3tgU7zVc3OO$&sBD)KxB)*%yzoOC78^RwUUm?6AbFh|sG2DT?+wunw=R&s({ZON8* z+%Gd!|1{5cMO)^&c<5>Qtf?*V(k+U#5J&p~DmG*Q3ky?3qltt~$&Xp50hk*78m) z-hNZ9nmyO=SvvLLdQ;ZHf*YbH?GLy@$=pT!#s{!&%kw7<*dypjpk38U^?q)y&LaN<=qLtnlPI8VESw>Vy$KFSAOg- z&Y=wWu(=&q6Jz$;utNP^_hpM!ynJ-MQSN}CeJ2zX(E*HipJ;Y^H}v^#2K0vS_;P?6 zJY7pbkPPOxiSKrVZtEwf4`z=uFfZ0i@H7`*3s|`TUKd<6qI^>8(-z;L(g%0l8t3QH zNmY+r2KGgg&B1#Q*e(Yo_U}`}#rnZp_KF8{CRhmtTMr{GZ#)wHe$m0a^U)b;tnm?{%g% zhG~5oa9~Hw|D>OEU_H(+X0B&?uVWNZ6T0u0Klq4)T2P01`3pad(u~SMNP+hj$SD)&^J)m{#kWiS#p03 z_S@K+HAWr?P>x3U=dv|U=iLK5uA}bcD*Ub(R^WbDS zK(=RYy8&FfWLV-C@*$DtBF>y+%rn~sO0Kipb~MNwl#|mr=Q>E^aMN+k)^pGgO6|q0 z`dUP{w0H6NOw^*p?3kalr+d?x7V}p?5z)#mk?(;f!|CLz%dm^8Q;RcejL%JE7c(>m zeO$w%By-rftJVny^N@c}eb_7CzdD)w3hzio5*>A=1hFn+Ees=MC`xg$?N z@bs2;r{*#@L7mo2f+HhWv0r*gGMBwJC6dw6!&fxLCjuq_Dn2!>azHB0k>1$>Zh0Lf zs+ly0t=$?u^zHkG)Y{yUwe^Ty!_w*R&=mXdNkXpuH|@|#k5ZcEkXA&{QMnmgW%6gK zWqHXF6qWc6`w;C`Tl{mOd0uFCUe8FUI%eg8=Z-m%){{Xqn(8~6RPn}Z4|*F+>b<$u zY`t%BX6iC_7m#<@lIPt41ocnBCnjr=)o^BKf0bk!xh}KQ#Jj;_(?FfR;gCrnSW38i zg8JXadk%dPBo6Fvgzw(gJPSX$ii@_E6YcK#)yR_AzPV58IW#&HVZZxf2C1s?#<?R~^euM-g1QWeI0TyL>O<2h+7G z(E$>$l(b72r&dX56{rHE1|@i<1GzAZfjZy)l@We7m)TM2kmM;uFZe#NqNP%5&<}Pa zvIF_w9}w7o@%(Q611>8uWwIezIqVX zwbHxxFx7cMnxSSS?oAM1iNsa+xC|(x@BJ!lm;I7Bj>q@eom10v92FkqK7G0bM+K)d zeKnC6$Z3!7JnI1zkc2~2dv8rhhIJH!OQVx0-_9ocVlT$85!>hb91vUJqpFAgS z*W~U7_qDX41iC5UYCr?9mAooaQ^r?vUGo-BFo< z5V9kz;IpehbjXItw*+ZX zYP1*}Zy>1^XReP}@)BH`$7mM^J;$Y~EEsp);ZWJR=&OR%3^U2@<^S5}W^x&GAo_3& z_FJE+fiz+r==)|K&Wxu+7h17R=k<51nlFpnOE<-^7|;un8AB4E*f|yz2kXwF5??#r zQC;+4M@d>XHaR@gSXD;<4KS)H9ka=zbKA$sg621H6qRr=Tk{YLXkt?kSNaj`{q_gL zb`76owtCvl2ueNaoHnBYlvbZ#n$Cy^LL=7I2*J z_NYkmnK$Dxotq2Vt6EvFY+RZ%c*GE9DVwEqR`=OG2YDl&}U2I zZb-~H2BBO{o7hX6TSN}(1Ym?^Up5Ba=h$DvG$v2{}w?d<`l+k{D}IrkSV9{xhmcL9 zRMQ@fv=d`z8;mOF#Qb%rqMM9p9!@(uBrRSp%8;jNjj6I-BPXr)?SlAwmY`o0be@!3 zUtEEox&d8==q~zZ=9UgCQWXy8Vm{}NcW!2h_$gAfs{zU6BTVwqCn*OwwlVIY|D}&x z!*Tz}=@DF&nh4fEr)E!_PAN(K+iyp@-ZNg~MAYmSIb~oH-#yZQY4j*oA&};B+F&xl zA--l)fAsy=mlXg+qI?Ufy$xoTKf}7=(Q36Pl`zqE1=|khpvs$J8mE#VerjqTW~HQ_ zS8r`QTH)Z2h$G0czOqil(w%0V?I-d5sLEtgT}@D90EhGRMUwL^O?NV#$1f=Cpo)~0v?*3sxmCvqVUGKORF0co*Yb2 zev4;>*J&>#7B4Fk0NQgm^5_gU>05A_yalD-Svd@>mhMKZlh3k%!#TCXTkE80dtPND zL~|%xS#&sXW+wC9`q6pXOve5OrIZta!8fpf?N32Q=6Rf2+P>&j)!yMFSnduwa%`2h zBP4b&c9_IJUSew-z0Z(X-F8e`2D{{ao^CCuH%*k(30+K@P(CR}&{CPofd%p4G!A9g z>+X)=6hi(SXyS8Fnr6hOH_XWvZ828)X=_r=e4)EBRHznAMsVA#6Y`duvKO$6Ti|Zu z|BO-ilU%(P@%pnp{~=w(E1tc3Xerp_3L~_nl=?Y6q_N;ASFeOYya_#y@$neEWrU<* zr?>yft3AjZ#3M}L_|<@#2BO_Iuu=qSZWxV@xE{`XwbGed7z&dg!$s>6{R5y3%7EC* zH`#gGpGz6~1NL8gXC}V@wjW3So&j+zIl#A479z!xihjM)vH!oCj}JdCzH!#nUT)B< zx1Of{zVuf_F21bqQ;@H$N?A-Ua70m$R`KUPo7gtt8)Bs-Fmc&&7&K)%crVkv8iP4~ z26y>HHjQ**aY?^Test%Kgu1o(>e31jF>USYYEJ$ByFA3g=S0&ZLS+OO24nG}L-N5sSXKxf^|Mx~T?pwPw`3%=l`tL3X|~pGOI??TfMycX zI`qv_+01k(?`#^BH{IAPiHsI5-*TK-xwpE3;~TP?szb>Z=!i6pvT)<~Gg6r+53BHa z7k`quTSwNqo=+v#apX>|ez>zKRTq!A#d;H&0-4 z7Pajx$9{?r`wN?V9M|+u7uB}1UC;DAWcn5}s1i%hxgm!5XBR8c zj`JABd|D(a1vxAC5O;xClM}6}X~aeYf8A2452U`ArQw!L($QTIuU5M?%-*c4WqZwG#N*oE0g_KNo)!d3Uyfez$PY!DmMP0*n8u7if^jhtem*Km zfa__wvj`d%q<}S*#{WO|4?_SCFv7<#(YhjyLo5*?mAN7JBIx_U>t{HXCw-vd8Vr3L z71AANINCk*K7? zqby4%9KRvO)qRrE+y@}1H1}`um8K)PBzu*toji1Wo~c>?$AjCl>B%27XU#8x7XnF8 z864h`*2{SHMO4=0q0}M5uYrhs3{9#2+J1cWa|L8)a4h82s_V_!k(9>O+vB=G+q+2< z8|_{t-LAKWM332T^U%tMXkBMoL|p2H0};_XiS)99M4f5xK3x4-be)BNJ1tMP zsZ#0L1ZEcGY{0_|dU$cyTNn-sHp;*9`CoRd|C<6@%C*KVkoXbUFVNXb&=1@tOUaLy z69XYVi-*a)`71EvEd34e+J@dns;7s4Mao_{8mLZr?a=;O@7*N`g>yDojd54A8csGz z8zrXfU(toqbK>dC%fFI64K$C5QI4%yH-Fam7Y68`U7&s+KU4hNvZ|%ZPt@pUT8M{v z)Bn=y7Rxjc-TB6r0Ni*RgHGy=QOtekI>forh=|xWQV);i=32*<=XLgW-_wh5nqgsxnm0Ruexxi3z~L-qhuep4!pjl<4@H zc(ZEw6cleN_7hA#glw4HC4ZmEE#)rB?@EWZu1IBEz#AO*_3QB-S#47CItCPx%;=rq z9}H}@RSDeQor1{~N$;mG3q!t$X7?XLi}fFGQ$2aNQ`TD2iXCn;Y1WC+Gn zXW~mTD^fF);3S2*YOt4Q0Z@rEEgE#3!eqRLsYaULZSN^>wo2KD^7gT`lN~$w-e=!O zEy|`#cK4i~l~yja22WAWv_q+o3!j_qItk4u!PJ9E|CpkgCV%c0T9nIIMqwScjj0r9 z;H3dzI{6L|T1C{pvXr(^jBN_<3SB)kZ_F?U9$^tFLo*w_4UAW-Vg=A|A8+|6vcwIEr;l0LDx@Jd;VGWOj>kF*+CQopDk2riv?vu=b_mAa|TxE|FS5CA$a&#}G@--O_ z88+KLGpk6ufcK?{&nrlfY=4=2+;ngvt+J9hQ(TEc>`Zke<)zhaLVpP8kSR~d5AL0; zyNGzcC~pJ^q$e%spV;0g2QSk~>A#rDX@X^wDQb4&-K!;ypUL z7R_sfR7$bRvawZpCEdbWQAl5ic>V8U@f7s5`Ae}bL2{JnM}dU0BT{?h(NN)(`PDx| z-5^Tw2vFus)&F)V+F?GOLXy{@JSk#bQ3%`ng)vG(Q!e;WGZTUp|?o!^Fj5j90uz>to*#v;H^zQ#Mosz)V@a{}pnilY1$NnFw zqrKzpf-UabD&?sMA6ObsJa4gnIXuydc>J(5FP&l^d{HT^z>&wx{=ZP4`U9j@HWQ-y zcQ>$Q=!wTycN$g=(1k%=QLz@eNqbhsa=um%UlU zHSdKQjzJBT@nOJ>=01y+_=n?Va}AI7QI%(HMhluGB<-@32VgB}Z4@G|&tW*fCl}F@ zR^I|=GFS&h=}j#yIDsIZN4PiNN$O>#rD0_++HGa@vz5z98OI+LKtAyQWuT2dwP1!@ zmSLb=RC=50d@OXLV6mq&2aN+4)rntTuJ74xsxB2g8{BZAc5q>2B& zZlTR@lAAIL4bxn+r;8&6f*xGH|eMK;L%#yPO=a&;w&We|0N6HR9x%4Mxc|5us|6pH@ zd962JTwlksZD_N~e5aWkb|7lt>S3oeDg zt(p)Jg&~GW{Y!Y~Qki`qMh<|P!+S`@nJZ5>5Vlv2_+WgYDs5C0W7!51F6k$^?wcvP zP8`=xb{Ol*lhP4Hd#G^8m?vTxE95vFD8u@Ea1?hkkDzD}0u7dc*+P9WI!I~$fn=Ne85 zRdpSt^jI8PcPxv5{C>40!+u$Nvp2*-ZSB{(mZ@6tOHxVje0}4?h^aEr1Gkzzx_e8o zH;oaCPj=?Z6o>HtM69l(T{fknowh+W^dcfcoYPRN2fxcfvesMM!Rds+>>Jn#20wv!k18*^$|9!r1{vt=_tg z_5NYr(Ap8a6_J}y!4?fHAOp6UH#2CTwsVHpZ1OyEt+@{Tzk$5@S#yDb-F3ayP*{Jc zM`aMc?X}w3?=s6dYc!l0lkeHsF_!wus&21wPS1&yDb4z}0C;p5w&K9lHP@Pggmat+ z1<#EraC;A{T_^?60aTQI8|`3Y{B|a&N(_?=G?_ncAws<^&6gE|f*zxMa5g*J3o3nz zzjhJ*`7s2B*?3{Au6=YQ0|}!$I*%nQvG7{F;#Do#@7}3SQ|L9~7mwtTxfea>C)HTu zB0}K(Q3`)R@NrDZzwh>Z^yjCAv8y;=G6U-m96Jz^atSX_S$MR3f2IZM)t9uOt~W|& z;7|jqP?JQNXgeWgl@OFfu%EctJCj;Vo;z+IB$Q;4alUPe)cKfbWJ1QP7{1anG=bm` znk1@2UBX!2>6?T+kM=cSd$!5jn1ILME$9#Re*I~jXvfvTq3E3%q}up><~PodY~S8; z*Gjpx_ZUFC7g6utS=v}b@C(u{>+%2@x%H3@;F1b1V5I|5?Z5NR1??eL8or$wgE$nD zR=I3qnuGJ>CCm2thsS#1{FdP!i0%An^TX7#$m!*`@SVp%2m+-0!5iD?aSVI`LJ;dP z>tV;b$H~o{U{qwKf&b&;MMqcgN8Vc)D0rZMsr?JVKfJS*{+G`r+E=^For||sIVI+> zvauM2EZgj?)QFoxBE7hFo91TPvQV)Q|DGW091zNFYlW>5O1Z~ET&MevFw}zu+wGNe+(r6XNh)zqog zfPgJjjPRmO3k$uIQnh#DD{Wn>yt8XdmHw41>ZfrW@fup*A(aSfAovG3EnEJW z&!kdR4HT8h4<4V*!_2yVIVXOW@lo>a*lz5M8Xm3K;w>751Rc5Z=C;`ej7XJZ(O<_7 z5gxZt__&Q|=6`>KA#<13Ii*}Bimuj)K|0-RWTfk_%(#*U&n_Qvwgk2TSC?v)~h|D*qF&^ zSyD{L8z*pb1=r+j#VSH}^*Dk;RQ8`@pEZ%Y3~ZI>W-b-|ME4}!SXL^VDVdBlE{XHy zWzEsPM=*TYo>u7D*Xy|M%Qd}wYuLtZz-)D^_u0VlhDcpjcxEpP4nEw(=F^9-W8$dp z!8n9z+C#+5qd`PXlJuEUb0A z$Q$vy0BA#w0VbLVWnK`@ww6OGdb_X|zzeL=dS>zZ3h&c~P+QN&n1!#o$pp}znY_Un zxaP!0EqC$kDFcad34S;hnvv1ja_XWhuMYXxvFAOlb&D3e96mHHKNDREypp?*TX7Xl zw0QES=L=w9;{l8U-POS;2<)fL!6p$oMN~siQXhoIJ&He2+a|6`2;M(Im{*YHqr-b| zwBg_HjbZS~4uf)BN`ed>Z{YG<;LX1y>(yIMzq9^3{wihd@D!bEVq14Pr z7~F)O4yjib@7t!c#2jI>9~?53;WZ~$6VV5axs(d5{;UX5zcA`To+v0BpBrERK3;N` z`2YJ24gU_m!~U{?@A_5h-cgLwK_whb_LOIpn`<{K5UQw@5^ z<-#h(P+}FN{Q@^8E-Q#~@aek^@jzIDCHtV~_pGp!t#$uH9W992$T^)5?^}ADqMqUr z$82&4rwJl~k<4m)GEM~v3DBm2Nh*a?G$t52$%QENz8}l;e?*M_)fN&NYE;jNBfhA^ zZ3e&v8TNjSqg|abAL>a9JU{iL0`s2SQp;D_hE7z?N~PEGi+h#WrciPI6CBpx>mi<+ zEbf&Z(d)Xd=Gg=Iz6|HA@+MDBhhkn|l^F!2FBqx5-;F@8ZD4j-NZvp!T0 zWtBXr#gC0R;P|6|Pl4!RxkYh_>&h z2lRL5Id!mbHAHEc+ktGFRFlQUopTOl1I*I#okCt9n^q30#3dmY*uR$t{4G)1mRR+F zQT6U|N#Ae$cZZeRZ24_2UFBh$m1`a_T^{nl_HE0xOt!TmLnRN%$^)V)q8!>fxFqG9 zmRc&*I=J$HO5y>9QqdHJr70pR3L>cp0-~JxecFBh?#JW)(|^u=x+P|-tNo-whj58G@DQ_ecn)E433ruF`9iNoO>1JU_7UOL$;# zn6~>kd2Eki|g0kfp@A(8N~gfq&=e7DO@nzGTFlN^qq9n(3!o8EFjKG(X; zA&s0zR70?t(PsoMyN+g3!6-wG*&W?kv4EK=+ad}a{dy?Qx6sM5>h#TLst-NS?AS*2 zq+h?|2sdZE!D^wHpivWsvl4R7H$^)t!(v9im^M%jxdw(+LIm>K3n(Ca5>v*oA7}B> z7jxe_ufv#9SQ;|*4zKN@>v8_OCOlEG z9lbRF2cpw3lrd6?u+8(%4)3VZ2;lR)Ohk>W8y*`PF@J!Hb_&5@S{sDMjla$9Bp)i; z?E5AFa#Egx8%%rT2$iMCmKfTMx64wGiV==(UNxG8iZcyV=kJ;l^i>CGWwm<4-x3UG zak_`?3Sq;oiVfnP{4&hr1!B8WMle(p3=6^=>83@U)|+_u+LWzRo;1|flXMX$h*}w% zVK`dn{^@<(JxUQoM>4dzul-pCqg764+iCFB%4NPs)h_HoOLgDBUOnEQ2UYi;mSx&W z5coU%a4!(0jOL$tWmL!c0LH5FVs=P9726toy{RRc+$BlQe--~)bnN;x?FHnv^*u4c zGDi6naMLp~-#TZ26_PN;LRAK8xE5h&7HI&O#A>+T5X1`AE}w}E>$rCcNT9R1ixne^ zZAV$SLkh8pmtq6bwN^7(;1sr;-6T)Qe?)tM|RTGBa-Oz(SD&U z-)+^~7!z^VWs<)@j>LDPs1Ry4HDG$7*@>ow_676OFD{fZ@~W1(jmXa1#x#G z?S@E-HnJl!EbV`Iz_(;jKM2m#JydI#*!pE|Ika-6gcWj#dv_==VGMts28Lx#Z+vtE zoy5SrVSP3B5V<2)6&Wm?s`^oMxDtQCaPS=A^Y(C8HpV57`n+Mv8BS%Y@3UW5cYa6? z79BRP={g2ca=&+js;5YoY{TTpY#IL~4<3up`WAhPg1}h!iQZ6SuEGAG-QD5h6&YF- z)ftGH^~rOj$yOiD<~Nj z$p?CDosi!gRmxqs*1;M;bmNaib#X7xmUl~6 zrhaE>q+j>lLP?o_6BIyJde+EP8q@4JdGF2K)dgPCudyzPTGqD_MBU1ISSISuw6~<8 zvoddGKy}!SRKl=~BFf=II!<8rOD$v1`XrQu44@2+#^XjqR$HYtQ#rmC0p%>>wuBNq zSpOUaSnq)8WW6pFXPbWS%wE6w^S`&H#6sLa*ZXA8s*`&O8ty1mlbN46QxA5Py}Z>8 zVU3P>T?Z*^4|fE-)P-B$x=!I!_(w7u?=>)?Rxto>5q4+uHuoH;PvdjnZFV0skX7k# zL1cx6xjO>3fO*!qcnEdc4hHns9*ztC|DWBigXiL{)bsavSExLK6SQmYX!I!w2qa(I zJLCP`(Kj%g6$F|$&K>eI{p^c#xxtOIrMKt}~oE5kkhIWtHdvZ-5GSu&T z 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 noimage.png splash.png + icon.png diff --git a/src/camera.cpp b/src/camera.cpp index 3640513..6e67e7d 100644 --- a/src/camera.cpp +++ b/src/camera.cpp @@ -6,7 +6,7 @@ Camera::Camera(cam::Camera::Description desc) { - cameraId_ = desc.getHash(); + cameraId_ = desc.getId(); std::function cb = [this](cv::Mat image){newImage(Image(image, cameraId_));}; camera_ = new cam::Camera(cb); camera_->openCamera(desc); diff --git a/src/cameras.cpp b/src/cameras.cpp index 1351ad3..74deaa9 100644 --- a/src/cameras.cpp +++ b/src/cameras.cpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include Cameras::Cameras(uvosled* led): led_(led) { @@ -46,7 +48,7 @@ std::shared_ptr Cameras::getCamera(size_t id) std::vector desc = getCameras(); for(size_t i = 0; i < desc.size(); ++i) { - if(desc[i].getHash() == id) + if(desc[i].getId() == id) return cameras_[i]; } return std::shared_ptr(nullptr); @@ -62,14 +64,35 @@ bool Cameras::addCamera(const cam::Camera::Description& desc) return false; } - cameras_.back()->cam()->setTriggerMode(cam::Camera::TRIGGER_SOFTWARE); cameras_.back()->cam()->setExposureTime(exposrueTime_*1000000); - setFree(false); - cameraAdded(cameras_.back()); - qDebug()<<"Using camera"<id(); + + if(desc.getVendor().find("Photonfocus") != std::string::npos) + { + qDebug()<<"Mitiagting broken PhotonFocus single shot mode"; + std::shared_ptr camera = cameras_.back(); + camera->cam()->setTriggerMode(cam::Camera::TRIGGER_FREE); + camera->cam()->setAcquisitionMode(cam::Camera::MODE_FREE); + camera->cam()->setFrameRate(10); + camera->cam()->startAcquisition(); + QTimer::singleShot(2000, [camera, this](){finishAddCamera(camera);}); + } + else + { + finishAddCamera(cameras_.back()); + } + return true; } +void Cameras::finishAddCamera(std::shared_ptr camera) +{ + camera->cam()->setTriggerMode(cam::Camera::TRIGGER_SOFTWARE); + setFree(free_); + connect(camera.get(), &Camera::newImage, this, &Cameras::imageRecived); + qDebug()<<"Using camera"<id(); + cameraAdded(camera); +} + void Cameras::trigger() { for(auto& camera : cameras_) @@ -108,6 +131,11 @@ bool Cameras::stop() return ret; } +void Cameras::disable(bool disable) +{ + disable_ = disable; +} + bool Cameras::setFree(bool free) { stop(); @@ -117,7 +145,10 @@ bool Cameras::setFree(bool free) for(auto& camera : cameras_) { if(!camera->cam()->setAcquisitionMode(free ? cam::Camera::MODE_FREE : cam::Camera::MODE_SINGLE)) + { + qDebug()<<"failed to set single on camera"<id(); ret = false; + } } return ret; } @@ -130,34 +161,40 @@ bool Cameras::setExposureTime(double exposureTime) for(auto& camera : cameras_) { if(!camera->cam()->setExposureTime(exposrueTime_*1000000)) + { + qDebug()<<"failed to set exposure on camera"<id(); ret = false; + } } return ret; } void Cameras::imageRecived(Camera::Image img) { - bool allreadyUpdated = false; - for(auto& camera : cameras_) + if(!disable_) { + bool allreadyUpdated = false; for(auto& image : images_) { - if(image.cameraId == camera->id()) + if(image.cameraId == img.cameraId) { allreadyUpdated = true; - goto FOUND; + break;; } } + + if(!allreadyUpdated) + images_.push_back(img); + + qDebug()<<"Recived"<> cameras_; @@ -30,11 +31,12 @@ private: private slots: void imageRecived(Camera::Image img); + void finishAddCamera(std::shared_ptr camera); signals: void cameraRemoved(std::shared_ptr camera); void cameraAdded(std::shared_ptr camera); - void newImages(std::vector images_); + void newImages(std::vector images); public slots: @@ -55,6 +57,7 @@ public: Cameras(uvosled* led = nullptr); void load(QSettings& settings); void store(QSettings& settings); + void disable(bool disable); }; #endif // CAMERAS_H diff --git a/src/imagepipeline.cpp b/src/imagepipeline.cpp index 469aca7..964aed8 100644 --- a/src/imagepipeline.cpp +++ b/src/imagepipeline.cpp @@ -1,37 +1,96 @@ #include "imagepipeline.h" #include +#include +#include #include #include +#include ImagePipeline::ImagePipeline(Cameras* cameras, QObject *parent): QObject(parent), cameras_(cameras) { + connect(cameras_, &Cameras::newImages, this, &ImagePipeline::apply); } cv::Mat ImagePipeline::process(const Profile profile, std::vector images) { + qDebug()<<__FUNCTION__<<"got"< remapedImages; remapedImages.reserve(images.size()); - for(Camera::Image& image : images) + try { - for(auto& camera : profile.cameras) + if(profile.nodistort && images.size() > 0) { - if(camera.id == image.cameraId) + for(auto& image : images) { - if(camera.darkmap.data) - image.mat = image.mat - camera.darkmap; - remapedImages.push_back(applyRemap(image.mat, camera.remapMap)); - break; + if(profile.cameras[0].id == image.cameraId) + { + RemapedImage img; + img.origin.x = 0; + img.origin.y = 0; + image.mat.copyTo(img.image); + if(profile.cameras[0].darkmap.data) + img.image = img.image - profile.cameras[0].darkmap; + remapedImages.push_back(img); + break; + } + } + } + else + { + for(Camera::Image& image : images) + { + qDebug()<<__FUNCTION__<<"image cam id"< 0) + { + cv::Mat output = simpleStich(remapedImages); + output.convertTo(output, CV_32FC1, 1.0/255.0, 0); - if(profile.lightmap.data) - normalize(output, profile.lightmap); - return output; + if(profile.lightmap.data) + normalize(output, profile.lightmap); + + if(profile.calcurve.data) + applyCurve(output, profile.calcurve); + return output; + } + else + { + qDebug()<<"Image pipe failure insufficant matched images"; + return cv::Mat(); + } } void ImagePipeline::apply(std::vector images) @@ -41,6 +100,7 @@ void ImagePipeline::apply(std::vector images) futureImageWatchers_.push_back(new QFutureWatcher()); connect(futureImageWatchers_.back(), &QFutureWatcher::finished, this, &ImagePipeline::imageFinished); + statusMsg("Processing"); QFuture futureImage = QtConcurrent::run(&ImagePipeline::process, profile_, images); futureImageWatchers_.back()->setFuture(futureImage); } @@ -51,11 +111,20 @@ void ImagePipeline::setProfile(const Profile& profile) profile_ = profile; cameras_->setExposureTime(profile_.exposureTime); cameras_->setLighting(profile_.lighting); + qDebug()<<"setting profile "<getCameras())) { invalid_ = true; sigInvalidProfile("A camera required for this profile is not available"); } + statusMsg("set profile " + profile_.getName()); } void ImagePipeline::imageFinished() @@ -65,11 +134,18 @@ void ImagePipeline::imageFinished() if(futureImageWatchers_[i]->isFinished()) { cv::Mat result = futureImageWatchers_[i]->result(); - sigResult(Camera::Image(result, 0)); - delete futureImageWatchers_[i]; - futureImageWatchers_.erase(futureImageWatchers_.begin()+i); - i--; + if(result.data) + { + sigResult(Camera::Image(result, 0)); + delete futureImageWatchers_[i]; + futureImageWatchers_.erase(futureImageWatchers_.begin()+i); + i--; + statusMsg("Finished"); + } + else + { + sigInvalidProfile("Image pipe failure"); + } } } } - diff --git a/src/imagepipeline.h b/src/imagepipeline.h index 5ca8d86..09c09d2 100644 --- a/src/imagepipeline.h +++ b/src/imagepipeline.h @@ -30,6 +30,7 @@ private slots: signals: void sigInvalidProfile(QString message); void sigResult(Camera::Image image); + void statusMsg(QString msg); public slots: void setProfile(const Profile& profile); diff --git a/src/main.cpp b/src/main.cpp index 4bdf7ad..9bac951 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "cameras.h" #include "./ui/cameradialog.h" @@ -17,6 +18,7 @@ #include "./ui/profiledialog.h" #include "imagepipeline.h" + const char* organziation = "UVOS"; const char* application = "UVOS"; const char* version = "UVOS"; @@ -44,13 +46,18 @@ void showProfileDialog(Cameras* cameras) { qDebug()<<__FUNCTION__; cameras->stop(); + cameras->disable(true); + std::vector descs = cameras->getCameras(); ProfileDialog diag(cameras); diag.show(); diag.exec(); + cameras->disable(false); } int main(int argc, char *argv[]) { + Log::level = Log::WARN; + QApplication a(argc, argv); QCoreApplication::setOrganizationName("UVOS"); QCoreApplication::setOrganizationDomain("uvos.xyz"); @@ -61,11 +68,12 @@ int main(int argc, char *argv[]) splash.show(); QDir().mkpath(Profile::profileLocation()); - QDir().mkpath(CameraSetup::camerasLocation()); qRegisterMetaType("cv::Mat"); qRegisterMetaType("size_t"); + qRegisterMetaType("Camera::Image"); qRegisterMetaType("Image"); + qRegisterMetaType>("std::vector"); QSettings settings(QSettings::IniFormat, QSettings::UserScope, QCoreApplication::organizationName(), QCoreApplication::applicationName()); @@ -100,11 +108,16 @@ int main(int argc, char *argv[]) QObject::connect(&w, &MainWindow::sigEditProfiles, [&cameras, &w](){showProfileDialog(&cameras); w.refreshProfiles();}); QObject::connect(&pipe, &ImagePipeline::sigResult, w.mainImageViewer(), &CvImageViewer::setImage, Qt::QueuedConnection); + QObject::connect(&pipe, &ImagePipeline::sigInvalidProfile, &w, &MainWindow::profileInconpatible); QObject::connect(&w, &MainWindow::sigProfile, [&pipe](QString name) { Profile profile; profile.load(name); + if(profile.cameras.size() != 0) + qDebug()<<"loading profile"< #include -void CameraSetup::store() const +void CameraSetup::store(const QString &filename) const { - if(darkmap.data) - cv::imwrite(darkmapLocation(id).toStdString(), darkmap); + cv::FileStorage matf(filename.toStdString(), cv::FileStorage::APPEND); if(remapMap.xMat.data) - saveRemapMap(remapMap, remapMapLocation(id).toStdString()); + { + matf<<("_"+QString::number(id)+"_xmat").toStdString()<>bgmask; matf.release(); } -void CameraSetup::loadRemapMaps(const QString &filename) -{ - remapMap = loadRemapMap(filename.toStdString()); -} - -void CameraSetup::load(size_t cameraId) +void CameraSetup::load(const QString &filename, size_t cameraId) { id = cameraId; - loadDarkMap(darkmapLocation(id)); - loadRemapMaps(remapMapLocation(id)); - loadBgMask(bgmaskLocation(id)); -} - -QString CameraSetup::camerasLocation() -{ - return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/cameras/"; -} - -QString CameraSetup::darkmapLocation(size_t cameraId) -{ - return camerasLocation() + QString::number(cameraId) + ".darkmap.png"; -} - -QString CameraSetup::bgmaskLocation(size_t cameraId) -{ - return camerasLocation() + QString::number(cameraId) + "bgmask.mat"; -} - -QString CameraSetup::remapMapLocation(size_t cameraId) -{ - return camerasLocation() + QString::number(cameraId) + "remap.mat"; + cv::FileStorage matf(filename.toStdString(), cv::FileStorage::READ); + if (matf.isOpened()) + { + matf[("_"+QString::number(id)+"_darkmap").toStdString()]>>darkmap; + matf[("_"+QString::number(id)+"_bgmask").toStdString()]>>bgmask; + matf[("_"+QString::number(id)+"_xmat").toStdString()]>>remapMap.xMat; + matf[("_"+QString::number(id)+"_ymat").toStdString()]>>remapMap.yMat; + matf[("_"+QString::number(id)+"_origin").toStdString()]>>remapMap.topLeftCoordinate; + } + else + { + qDebug()<<"could not open file"<(id)); settings.setValue(GROUP + QString("/exposureTime"), exposureTime); settings.setValue(GROUP + QString("/name"), name_); + settings.setValue(GROUP + QString("/nodistort"), nodistort); if(lightmap.data) cv::imwrite((profileLocation() + QString::number(id) + ".lightmap.png").toStdString(), lightmap); @@ -93,10 +72,18 @@ void Profile::store(QSettings& settings) const { const CameraSetup& camera = cameras[i]; settings.setArrayIndex(i); - camera.store(); + camera.store(profileLocation() + name_ + ".profile.mat"); settings.setValue("id", static_cast(camera.id)); } settings.endArray(); + + cv::FileStorage matf((profileLocation() + name_ + ".calcurve.mat").toStdString(), cv::FileStorage::WRITE); + matf<<"cal"<>calcurve; + matf.release(); + + if(calcurve.data && (calcurve.type() != CV_32FC1 || calcurve.rows != 2)) + calcurve.release(); + + cv::FileStorage matfl((profileLocation() + name_ + ".lightmap.mat").toStdString(), cv::FileStorage::READ); + matfl["cal"]>>lightmap; + matfl.release(); } void Profile::load(const QString& name) @@ -164,15 +163,43 @@ QList Profile::avaiableProfiles() return ret; } -void Profile::setCamerasSetupsFromDescription(const std::vector& desc) +void Profile::setCamerasSetupsFromDescription(const std::vector& descs) { - cameras.clear(); - cameras.reserve(desc.size()); - for(size_t i = 0; i < desc.size(); ++i) + for(size_t i = 0; i < cameras.size(); ++i) { - CameraSetup tmp; - tmp.load(desc[i].getHash()); - cameras.push_back(tmp); + bool found = false; + for(auto& cameraDesc : descs) + { + if(cameras[i].id == cameraDesc.getId()) + { + found = true; + break; + } + } + if(!found) + { + cameras.erase(cameras.begin()+i); + --i; + } + } + + for(size_t i = 0; i < descs.size(); ++i) + { + bool found = false; + for(auto& camera : cameras) + { + if(camera.id == descs[i].getId()) + { + found = true; + break; + } + } + if(!found) + { + CameraSetup tmp; + tmp.load(profileLocation() + name_ + ".profile.mat", descs[i].getId()); + cameras.push_back(tmp); + } } } @@ -184,7 +211,7 @@ bool Profile::camerasSufficant(const std::vector& desc bool found = false; for(auto& camera : desc) { - if(camera.getHash() == cameraProfile.id) + if(camera.getId() == cameraProfile.id) { found = true; break; diff --git a/src/profile.h b/src/profile.h index 561787d..3eeae19 100644 --- a/src/profile.h +++ b/src/profile.h @@ -16,15 +16,8 @@ public: RemapMap remapMap; cv::Mat darkmap; cv::Mat bgmask; - void store() const; - void load(size_t cameraId); - void loadBgMask(const QString& filename); - void loadDarkMap(const QString& filename); - void loadRemapMaps(const QString& filename); - static QString camerasLocation(); - static QString remapMapLocation(size_t cameraId); - static QString bgmaskLocation(size_t cameraId); - static QString darkmapLocation(size_t cameraId); + void store(const QString& filename) const; + void load(const QString& name, size_t cameraId); }; class LightingSetup @@ -50,7 +43,9 @@ public: LightingSetup lighting; double exposureTime = 1.0/60.0; cv::Mat lightmap; + cv::Mat calcurve; std::vector cameras; + bool nodistort = false; Profile(const QString& name = "Unamed"); void store(QSettings& settings) const; diff --git a/src/ui/cameradialog.ui b/src/ui/cameradialog.ui index b01d46c..8c8e927 100644 --- a/src/ui/cameradialog.ui +++ b/src/ui/cameradialog.ui @@ -16,6 +16,10 @@ Select Cameras + + + :/images/icon.png:/images/icon.png + @@ -46,7 +50,9 @@
./src/ui/cameralistwidget.h
- + + + buttonBox diff --git a/src/ui/cameralistwidget.cpp b/src/ui/cameralistwidget.cpp index d7e1dc8..879cfd1 100644 --- a/src/ui/cameralistwidget.cpp +++ b/src/ui/cameralistwidget.cpp @@ -20,7 +20,7 @@ void CameraListWidget::setConfigured(std::vector configured) setHorizontalHeaderItem(1, new QTableWidgetItem("Configured")); for(size_t i = 0; i < desc_.size() && i < configured.size(); ++i) { - setItem(static_cast(i), 1, new QTableWidgetItem("No")); + setItem(static_cast(i), 1, new QTableWidgetItem(configured[i] ? "Yes" : "No")); qDebug()<<"Set item "<& d setRowCount(static_cast(desc_.size())); for(size_t i = 0; i < desc_.size(); ++i) { - setItem(static_cast(i), 0, new QTableWidgetItem((desc_[i].vendor + " " + desc_[i].model).c_str())); + setItem(static_cast(i), 0, new QTableWidgetItem((desc_[i].getVendor() + " " + desc_[i].getModel()).c_str())); } } diff --git a/src/ui/configurecameradialog.cpp b/src/ui/configurecameradialog.cpp index b7622c0..b311528 100644 --- a/src/ui/configurecameradialog.cpp +++ b/src/ui/configurecameradialog.cpp @@ -7,20 +7,26 @@ #include #include #include +#include ConfigureCameraDialog::ConfigureCameraDialog(const CameraSetup& setup, std::shared_ptr camera, double exposureTime, QWidget *parent): QDialog(parent), setup_(setup), camera_(camera), + profileExposure_(exposureTime), ui(new Ui::ConfigureCameraDialog) { ui->setupUi(this); + qDebug()<<"profileExposure_"<doubleSpinBox->setValue(profileExposure_); + setExposure(profileExposure_); - ui->doubleSpinBox->setValue(exposureTime); + uint64_t min, max; + camera_->cam()->getExposureTimeLimits(min, max); + ui->doubleSpinBox->setMaximum(max/1000000.0); + ui->doubleSpinBox->setMinimum(min/1000000.0); - connect(ui->pushButtonBgLoad, &QPushButton::clicked, this, &ConfigureCameraDialog::loadBg); - connect(ui->pushButtonRemapLoad, &QPushButton::clicked, this, &ConfigureCameraDialog::loadRemap); - connect(ui->pushButtonDarkImageLoad, &QPushButton::clicked, this, &ConfigureCameraDialog::loadDark); + connect(ui->doubleSpinBox, QOverload::of(&QDoubleSpinBox::valueChanged), this, &ConfigureCameraDialog::setExposure); connect(ui->pushButtonBgClear, &QPushButton::clicked, [this](){setup_.bgmask.release(); checkConfig();}); connect(ui->pushButtonRemapClear, &QPushButton::clicked, [this](){setup_.remapMap = RemapMap(); checkConfig();}); connect(ui->pushButtonDarkImageClear, &QPushButton::clicked, [this](){setup_.darkmap.release(); checkConfig();}); @@ -38,45 +44,12 @@ ConfigureCameraDialog::~ConfigureCameraDialog() delete ui; } - - -void ConfigureCameraDialog::loadBg() +void ConfigureCameraDialog::setExposure(double value) { - QString filename = QFileDialog::getOpenFileName(this, tr("Open Background Mask"), CameraSetup::camerasLocation(), "OpenCV Mat (*.mat)"); - if(!filename.isEmpty()) - { - setup_.loadBgMask(filename); - if(!setup_.bgmask.data) - QMessageBox::warning(this, "Warning", "Could not load " + filename); - checkConfig(); - } -} - -void ConfigureCameraDialog::loadRemap() -{ - QString filename = QFileDialog::getOpenFileName(this, tr("Open Background Mask"), CameraSetup::camerasLocation(), "OpenCV Mat (*.mat)"); - if(!filename.isEmpty()) - { - setup_.loadRemapMaps(filename); - if(!setup_.remapMap.xMat.data || !setup_.remapMap.yMat.data) - { - QMessageBox::warning(this, "Warning", "Could not load " + filename); - setup_.remapMap = RemapMap(); - } - checkConfig(); - } -} - -void ConfigureCameraDialog::loadDark() -{ - QString filename = QFileDialog::getOpenFileName(this, tr("Open Background Mask"), CameraSetup::camerasLocation(), "Image (*.png)"); - if(!filename.isEmpty()) - { - setup_.loadDarkMap(filename); - if(!setup_.darkmap.data) - QMessageBox::warning(this, "Warning", "Could not load " + filename); - checkConfig(); - } + if(!camera_->cam()->setExposureTime(value*1000000.0)) + QMessageBox::warning(this, "Warning", "Failed to set exposure"); + else + qDebug()<<"set exposure to "< points = detectCharucoPoints(img.mat, false); if(points.size() < 8) { @@ -117,13 +95,13 @@ void ConfigureCameraDialog::gotImage(Camera::Image img) break; } RemapMap map; - if(createRemapMap(img.mat, map, points)) + if(createRemapMap(masked, map, points)) setup_.remapMap = map; else QMessageBox::warning(this, "Failed", "Error creating map"); for(size_t i = 0; i < points.size(); ++i) - cv::circle(img.mat, points[i].point, 5, cv::Scalar(0,255,0), 1); - ui->widget_4->setImage(Camera::Image(setup_.bgmask, camera_->id())); + cv::circle(img.mat, points[i].point, img.mat.cols/50, cv::Scalar(255,255,255), img.mat.cols/200); + ui->widget_4->setImage(Camera::Image(img.mat, camera_->id())); mode_ = MODE_IDLE; break; } @@ -157,6 +135,8 @@ void ConfigureCameraDialog::captureDark() { QMessageBox::information(this, "Cover lense", "Please cover the lense of the camera."); mode_ = MODE_DARK_GET; + ui->doubleSpinBox->setValue(profileExposure_); + setExposure(profileExposure_); takeImage(); } @@ -165,7 +145,7 @@ void ConfigureCameraDialog::accept() if(checkConfig()) QDialog::accept(); else - QMessageBox::warning(this, "Unfinished", "Can not accept unfinished camera setup"); + QMessageBox::information(this, "Unfinished", "Can not accept unfinished camera setup"); } bool ConfigureCameraDialog::checkConfig() @@ -182,5 +162,5 @@ bool ConfigureCameraDialog::checkConfig() ui->ledDark->setLit(darkMapOK); ui->ledRemap->setLit(remapMapOk); - return bgOk && remapMapOk && darkMapOK; + return remapMapOk; } diff --git a/src/ui/configurecameradialog.h b/src/ui/configurecameradialog.h index ec5d60f..1566b7d 100644 --- a/src/ui/configurecameradialog.h +++ b/src/ui/configurecameradialog.h @@ -22,25 +22,27 @@ class ConfigureCameraDialog : public QDialog std::shared_ptr camera_; int mode_ = MODE_IDLE; cv::Mat fgImage; + double profileExposure_; private: bool checkConfig(); void gotImage(Camera::Image img); private slots: - void loadBg(); - void loadRemap(); - void loadDark(); void captureBg(); void captureRemap(); void captureDark(); void takeImage(); + void setExposure(double value); + +public slots: + void accept() override; public: explicit ConfigureCameraDialog(const CameraSetup& setup, const std::shared_ptr camera, double exposureTime = 1.0/60, QWidget *parent = nullptr); ~ConfigureCameraDialog(); CameraSetup getCameraSetup(){return setup_;} - void accept() override; + private: Ui::ConfigureCameraDialog *ui; diff --git a/src/ui/configurecameradialog.ui b/src/ui/configurecameradialog.ui index 4bf1a50..94fd5f5 100644 --- a/src/ui/configurecameradialog.ui +++ b/src/ui/configurecameradialog.ui @@ -6,8 +6,8 @@ 0 0 - 466 - 438 + 545 + 614 @@ -31,6 +31,29 @@ 6 + + + + + 0 + 0 + + + + + 40 + 40 + + + + + + + + Create + + + @@ -48,13 +71,6 @@ - - - - Background image - - - @@ -62,32 +78,6 @@ - - - - false - - - Clear - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - @@ -98,57 +88,40 @@ - - + + - Create + Background image - - - - - + + - Remap Map + Show - - - - Load - - - - - - - Load - - - - - + + - + 0 0 - 0 - 0 + 40 + 40 - - + + - Load + Show @@ -159,10 +132,36 @@
- - + + + + + 0 + 0 + + + + + 40 + 40 + + + + + + - + Remap Map (required) + + + + + + + false + + + Clear @@ -182,11 +181,14 @@ + + 5 + 1.000000000000000 - 0.100000000000000 + 0.001000000000000 @@ -219,18 +221,18 @@
- - Led - QWidget -
./src/ui/led.h
- 1 -
CvImageViewer QWidget
./src/ui/cvimageviewer.h
1
+ + Led + QWidget +
./src/ui/led.h
+ 1 +
diff --git a/src/ui/cvimageviewer.cpp b/src/ui/cvimageviewer.cpp index 8d380c7..3b864df 100644 --- a/src/ui/cvimageviewer.cpp +++ b/src/ui/cvimageviewer.cpp @@ -1,21 +1,60 @@ #include "cvimageviewer.h" #include #include +#include +#include +#include CvImageViewer::CvImageViewer(QWidget *parent, size_t lastId) : QWidget(parent), - lastId_(lastId) + lastId_(lastId), + saveAction_("Save Image", nullptr) { + connect(&saveAction_, &QAction::triggered, this, &CvImageViewer::saveImage); qimage_.load(":/images/noimage.png"); + + imageContextMenu_.addAction(&saveAction_); } CvImageViewer::~CvImageViewer() { } +void CvImageViewer::saveImage() +{ + QString fileName; + if(origImage_.type() == CV_8UC3 || origImage_.type() == CV_8SC3 || origImage_.type() == CV_8UC1 || origImage_.type() == CV_8SC1) + { + fileName = QFileDialog::getSaveFileName(this, "Save Image", "./", "*.mat *.png" ); + } + else + { + fileName = QFileDialog::getSaveFileName(this, "Save Image", "./", "*.mat" ); + } + if(!fileName.isEmpty()) + { + QStringList tokens = fileName.split('.'); + if(tokens.back() != "mat" && tokens.back() != "png") + fileName.append(".mat"); + tokens = fileName.split('.'); + if(tokens.back() == "png") + { + imwrite(fileName.toStdString(), origImage_); + } + else + { + cv::FileStorage matf(fileName.toStdString(), cv::FileStorage::WRITE); + matf<<"image"<button() == Qt::RightButton) + { + saveAction_.setEnabled(origImage_.data); + imageContextMenu_.popup(event->globalPos()); + } + else if(origImage_.data && event->x() > imgrect_.x() && event->y() > imgrect_.y() && event->x() < imgrect_.x()+imgrect_.width() && event->y() < imgrect_.y()+imgrect_.height()) + { + int x = (event->x()-imgrect_.x())/static_cast(imgrect_.width())*origImage_.cols; + int y = (event->y()-imgrect_.y())/static_cast(imgrect_.height())*origImage_.rows; + qDebug()<= 0 && y >= 0 && x <= origImage_.cols && y < origImage_.rows) + sigValue(x, y, origImage_.at(y,x)); + } + else + { + + sigValue(x, y, 0); + } + } + QWidget::mousePressEvent(event); +} + void CvImageViewer::paintEvent(QPaintEvent* event) { Q_UNUSED(event) QPainter painter(this); - if(!fixedOnWidth_) - { - double ratio = qimage_.size().width() / qimage_.size().height(); - painter.drawImage(QRect((rect().width()-rect().height())/2, rect().y(), rect().height()*ratio, rect().height()), qimage_); - } + double ratio = qimage_.size().height() / (double)qimage_.size().width(); + if(rect().width()*ratio <= rect().height()) + imgrect_.setRect(0, (rect().height()-rect().width()*ratio)/2, rect().width(), rect().width()*ratio); else - { - double ratio = qimage_.size().height() / qimage_.size().width(); - painter.drawImage(QRect(rect().x(), (rect().height()-rect().width())/2, rect().width(), rect().width()*ratio), qimage_); - } + imgrect_.setRect((rect().width()-rect().height()/ratio)/2, 0, rect().height()/ratio, rect().height()); + painter.drawImage(imgrect_, qimage_); } diff --git a/src/ui/cvimageviewer.h b/src/ui/cvimageviewer.h index 58b0f77..f932a99 100644 --- a/src/ui/cvimageviewer.h +++ b/src/ui/cvimageviewer.h @@ -3,19 +3,32 @@ #include #include +#include #include "../cameras.h" class CvImageViewer : public QWidget { Q_OBJECT +private: + cv::Mat origImage_; cv::Mat image_; QImage qimage_; bool fixedOnWidth_ = false; size_t lastId_; + QMenu imageContextMenu_; + QAction saveAction_; + QRect imgrect_; + +private slots: + void saveImage(); protected: virtual void paintEvent(QPaintEvent* event) override; + virtual void mousePressEvent(QMouseEvent *event) override; + +signals: + void sigValue(size_t x, size_t y, double value); public slots: void setImage(Camera::Image img); @@ -23,6 +36,8 @@ public slots: public: explicit CvImageViewer(QWidget *parent = nullptr, size_t lastId = 0); void setFixedOnWidth(bool in){fixedOnWidth_ = in;} + cv::Mat getImage(){return origImage_;} + size_t lastId(){return lastId_;} ~CvImageViewer(); }; diff --git a/src/ui/editprofiledialog.cpp b/src/ui/editprofiledialog.cpp index 0bdaf99..9b54db9 100644 --- a/src/ui/editprofiledialog.cpp +++ b/src/ui/editprofiledialog.cpp @@ -2,6 +2,8 @@ #include "ui_editprofiledialog.h" #include #include +#include +#include #include "configurecameradialog.h" @@ -24,26 +26,91 @@ EditProfileDialog::EditProfileDialog(Cameras* cameras, const Profile profile, QW ui->checkBoxCh3->setChecked(profile_.lighting.mask & CHANNEL_C); ui->checkBoxCh4->setChecked(profile_.lighting.mask & CHANNEL_D); - if(profile_.cameras.size() == 0) - profile_.setCamerasSetupsFromDescription(cameras_->getCameras()); + ui->checkBoxNodistort->setChecked(profile_.nodistort); + + std::vector descs = cameras_->getCameras(); + profile_.setCamerasSetupsFromDescription(descs); + + if(profile_.cameras.size() > 0) + { + uint64_t min, max; + std::shared_ptr cam = cameras_->getCamera(profile_.cameras[0].id); + if(cam) + { + cam->cam()->getExposureTimeLimits(min, max); + ui->doubleSpinBoxExposure->setMaximum(max/1000000.0); + ui->doubleSpinBoxExposure->setMinimum(min/1000000.0); + } + } + + ui->calLed->setLit(profile_.calcurve.data); connect(ui->doubleSpinBoxBrightness, QOverload::of(&QDoubleSpinBox::valueChanged), [this](double in){profile_.lighting.brightness = in/100.0;}); - connect(ui->doubleSpinBoxExposure, QOverload::of(&QDoubleSpinBox::valueChanged), [this](double in){profile_.exposureTime = in;}); + connect(ui->doubleSpinBoxExposure, QOverload::of(&QDoubleSpinBox::valueChanged), [this](double in){profile_.exposureTime = in; invalidateCameras();}); connect(ui->lineEditName, &QLineEdit::textChanged, [this](QString in){profile_.setName(in);}); connect(ui->checkBoxCh1, &QCheckBox::clicked, this, &EditProfileDialog::setMask); connect(ui->checkBoxCh2, &QCheckBox::clicked, this, &EditProfileDialog::setMask); connect(ui->checkBoxCh3, &QCheckBox::clicked, this, &EditProfileDialog::setMask); connect(ui->checkBoxCh4, &QCheckBox::clicked, this, &EditProfileDialog::setMask); connect(ui->pushButton, &QPushButton::clicked, this, &EditProfileDialog::configureCamera); + connect(ui->checkBoxNodistort, &QCheckBox::stateChanged, [this](int state){profile_.nodistort = state == Qt::Checked ?: false; setConfigured();}); + connect(ui->pushButtonCalLoad, &QPushButton::clicked, this, &EditProfileDialog::loadCalcurve); + connect(ui->pushButtonLightmapLoad, &QPushButton::clicked, this, &EditProfileDialog::loadCalcurve); ui->listView->setCameras(cameras_->getCameras()); ui->listView->setSelectionMode(QAbstractItemView::SingleSelection); setConfigured(); } -void EditProfileDialog::setConfigured() +void EditProfileDialog::loadLightmap() +{ + QString fileName = QFileDialog::getOpenFileName(this, "Open Lightmap", "./", "*.mat"); + if(!fileName.isEmpty()) + { + profile_.calcurve.release(); + cv::FileStorage matf(fileName.toStdString(), cv::FileStorage::READ); + matf["image"]>>profile_.lightmap; + + if(matf.isOpened() && (!profile_.calcurve.data || profile_.calcurve.type() != CV_32FC1)) + { + profile_.lightmap.release(); + QMessageBox::warning(this, "Invalid file", "File selected dose not contain a valid lightmap"); + } + else if(!profile_.calcurve.data) + { + QMessageBox::warning(this, "Can no open", "Can not open file selected"); + } + matf.release(); + ui->ledLightmap->setLit(profile_.lightmap.data); + } +} + +void EditProfileDialog::loadCalcurve() +{ + QString fileName = QFileDialog::getOpenFileName(this, "Open Cal Curve", "./", "*.mat"); + if(!fileName.isEmpty()) + { + profile_.calcurve.release(); + cv::FileStorage matf(fileName.toStdString(), cv::FileStorage::READ); + matf["cal"]>>profile_.calcurve; + + if(matf.isOpened() && (!profile_.calcurve.data || profile_.calcurve.type() != CV_32FC1 || profile_.calcurve.rows != 2)) + { + profile_.calcurve.release(); + QMessageBox::warning(this, "Invalid file", "File selected dose not contain a valid cal curve"); + } + else if(!profile_.calcurve.data) + { + QMessageBox::warning(this, "Can no open", "Can not open file selected"); + } + matf.release(); + ui->calLed->setLit(profile_.calcurve.data); + } +} + +bool EditProfileDialog::setConfigured() { std::vector descs = cameras_->getCameras(); - std::vector configured(descs.size(), false); + std::vector configured(descs.size(), profile_.nodistort); for(size_t i = 0; i< profile_.cameras.size(); ++i) { @@ -52,12 +119,19 @@ void EditProfileDialog::setConfigured() { for(auto& camera : descs) { - if(camera.getHash() == profileCamera.id) + if(camera.getId() == profileCamera.id) configured[i] = true; } } } ui->listView->setConfigured(configured); + bool fullyConfigured = true; + for(bool config : configured) + { + if(!config) + fullyConfigured = false; + } + return fullyConfigured; } void EditProfileDialog::invalidateCameras() @@ -78,10 +152,12 @@ void EditProfileDialog::configureCamera() qDebug()<<"descs"< camera = cameras_->getCamera(profile_.cameras[i].id); if(camera) @@ -94,9 +170,18 @@ void EditProfileDialog::configureCamera() } } } + setConfigured(); } } +void EditProfileDialog::accept() +{ + if(!setConfigured()) + QMessageBox::information(this, "Unfinished", "Can not accept with unconfigured cameras"); + else + QDialog::accept(); +} + void EditProfileDialog::setMask() { uint8_t mask = (ui->checkBoxCh1->isChecked() ? CHANNEL_A : 0) | diff --git a/src/ui/editprofiledialog.h b/src/ui/editprofiledialog.h index 5bfc50c..6a899c8 100644 --- a/src/ui/editprofiledialog.h +++ b/src/ui/editprofiledialog.h @@ -15,13 +15,17 @@ class EditProfileDialog : public QDialog Profile profile_; Cameras* cameras_; - void setConfigured(); + bool setConfigured(); void invalidateCameras(); private slots: - void setMask(); void configureCamera(); + void loadCalcurve(); + void loadLightmap(); + +public slots: + virtual void accept() override; public: explicit EditProfileDialog(Cameras* cameras, const Profile profile = Profile(), QWidget *parent = nullptr); diff --git a/src/ui/editprofiledialog.ui b/src/ui/editprofiledialog.ui index 7e9783b..25d8a49 100644 --- a/src/ui/editprofiledialog.ui +++ b/src/ui/editprofiledialog.ui @@ -7,11 +7,11 @@ 0 0 539 - 485 + 652 - Profile + Edit Profile @@ -153,6 +153,9 @@ + + 5 + 1.000000000000000 @@ -176,9 +179,89 @@ + + + + Don't undisort camera image + + + + + + + 10 + + + + + Calibration curve: + + + + + + + + 0 + 0 + + + + + 40 + 40 + + + + + + + + + 0 + 0 + + + + Load + + + + + + + Lightmap: + + + + + + + + 0 + 0 + + + + + 40 + 40 + + + + + + + + Load + + + + + @@ -192,6 +275,12 @@ + + Led + QWidget +
./src/ui/led.h
+ 1 +
CameraListWidget QListView diff --git a/src/ui/mainwindow.cpp b/src/ui/mainwindow.cpp index 7782173..2cf7348 100644 --- a/src/ui/mainwindow.cpp +++ b/src/ui/mainwindow.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "../profile.h" @@ -17,9 +18,62 @@ MainWindow::MainWindow(QWidget *parent) connect(ui->actionProfile, &QAction::triggered, [this](bool checked){(void)checked; sigEditProfiles();}); connect(ui->comboBox, &QComboBox::currentTextChanged, this, &MainWindow::sigProfile); connect(ui->pushButtonCapture, &QPushButton::clicked, this, &MainWindow::sigCapture); + connect(ui->mainViewer, &CvImageViewer::sigValue, this, &MainWindow::setImageValue); + connect(ui->actionOpen, &QAction::triggered, [this](bool checked){(void)checked; openImage();}); + connect(ui->actionSave_2, &QAction::triggered, [this](bool checked){(void)checked; saveImage();}); refreshProfiles(); } +void MainWindow::setImageValue(size_t x, size_t y, double value) +{ + ui->lcdNumber_3->display((double)x); + ui->lcdNumber_2->display((double)y); + ui->lcdNumber->display(value); +} + +void MainWindow::saveImage() +{ + if(!ui->mainViewer->getImage().data) + { + QMessageBox::warning(this, "No image", "There is no image to save"); + return; + } + + QString fileName = QFileDialog::getSaveFileName(this, "Save Image", "./", "*.mat"); + if(!fileName.isEmpty()) + { + QStringList tokens = fileName.split('.'); + if(tokens.back() != "mat") + fileName.append(".mat"); + cv::FileStorage matf(fileName.toStdString(), cv::FileStorage::WRITE); + matf<<"image"<mainViewer->getImage(); + matf.release(); + } +} + +void MainWindow::openImage() +{ + QString fileName = QFileDialog::getOpenFileName(this, "Open Image", "./", "*.mat"); + if(!fileName.isEmpty()) + { + cv::Mat image; + cv::FileStorage matf(fileName.toStdString(), cv::FileStorage::READ); + matf["image"]>>image; + + if(matf.isOpened() && (!image.data || image.type() != CV_32FC1)) + { + image.release(); + QMessageBox::warning(this, "Invalid file", "File selected dose not contain a valid lightmap"); + } + else if(!image.data) + { + QMessageBox::warning(this, "Can no open", "Can not open file selected"); + } + matf.release(); + ui->mainViewer->setImage(Camera::Image(image, 0)); + } +} + void MainWindow::addCamera(std::shared_ptr camera) { viewers_.push_back(new CvImageViewer(this, camera->id())); @@ -28,6 +82,11 @@ void MainWindow::addCamera(std::shared_ptr camera) ui->viewerLayout->addWidget(viewers_.back()); } +void MainWindow::statusMsg(QString msg) +{ + ui->statusbar->showMessage(msg); +} + void MainWindow::removeCamera(std::shared_ptr camera) { for(size_t i = 0; i < viewers_.size(); ++i) @@ -93,4 +152,3 @@ MainWindow::~MainWindow() delete viewer; delete ui; } - diff --git a/src/ui/mainwindow.h b/src/ui/mainwindow.h index 5fd0f33..0f33bd9 100644 --- a/src/ui/mainwindow.h +++ b/src/ui/mainwindow.h @@ -16,6 +16,11 @@ class MainWindow : public QMainWindow private: std::vector viewers_; +private slots: + void setImageValue(size_t x, size_t y, double value); + void saveImage(); + void openImage(); + signals: void sigCapture(); void sigProfile(QString profileName); @@ -30,6 +35,7 @@ public slots: void removeCamera(std::shared_ptr camera); bool setProfile(const QString& profileName); QString getProfileName(); + void statusMsg(QString msg); public: MainWindow(QWidget *parent = nullptr); diff --git a/src/ui/mainwindow.ui b/src/ui/mainwindow.ui index 3ae254c..784b7f2 100644 --- a/src/ui/mainwindow.ui +++ b/src/ui/mainwindow.ui @@ -11,7 +11,11 @@ - MainWindow + Lubricant Detector + + + + :/images/icon.png:/images/icon.png @@ -27,6 +31,9 @@ + + Qt::ScrollBarAsNeeded + true @@ -68,7 +75,7 @@ 0 0 467 - 353 + 313 @@ -79,6 +86,55 @@
+ + + + 10 + + + + + Qt::LeftToRight + + + X + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Y + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Value + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + @@ -146,7 +202,8 @@ File - + + @@ -164,6 +221,9 @@ Quit + + Ctrl+Q + @@ -180,6 +240,22 @@ Save + + + Save + + + Ctrl+S + + + + + Open + + + Ctrl+O + + @@ -189,6 +265,8 @@ 1 - + + + diff --git a/src/ui/profiledialog.cpp b/src/ui/profiledialog.cpp index c6fdaec..a6f04ff 100644 --- a/src/ui/profiledialog.cpp +++ b/src/ui/profiledialog.cpp @@ -34,7 +34,7 @@ void ProfileDialog::editProfile() QMessageBox::critical(this, "Profile Incompatible", "Not all cameras need for this profile are available"); return; } - EditProfileDialog dialog(cameras_, profile); + EditProfileDialog dialog(cameras_, profile, this); dialog.show(); int ret = dialog.exec(); if(ret == QDialog::Accepted) diff --git a/src/ui/profiledialog.ui b/src/ui/profiledialog.ui index c2b3e9c..9c07933 100644 --- a/src/ui/profiledialog.ui +++ b/src/ui/profiledialog.ui @@ -13,6 +13,10 @@ Profiles + + + :/images/icon.png:/images/icon.png + @@ -61,7 +65,9 @@ - + + + buttonBox